Specimen / 3d

Ridged Mountain Terrain

A procedural snow-mountain scene. A GLSL POP compute shader displaces a grid into ridged-multifractal peaks that morph in place, shaded by a snow/rock GLSL MAT with elevation-based snow, sun/sky lighting, and atmospheric haze, composited under a procedural sky.

advanced 20 operators

Node graph preview

Ridged Mountain Terrain graph

inert preview
Show raw TDN YAML

Paste in TouchDesigner with Embody running.

format: tdn
version: '2.0'
build: null
generator: Embody/6.0.15
td_build: 099.2025.32820
source_file: Embody-6.15.toe
exported_at: '2026-06-11T03:24:04Z'
network_path: /specimen_lab/noise_terrain
options:
  include_dat_content: true
  include_storage: true
type_defaults:
  infoDAT:
    flags:
    - viewer
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  textDAT:
    parameters:
      language: glsl
    flags:
    - viewer
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  mergePOP:
    flags:
    - viewer
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  circlePOP:
    parameters:
      connectivity: surface
    flags:
    - viewer
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  primitivePOP:
    parameters:
      addpts: true
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  tubePOP:
    parameters:
      orient: z
      rows: 2
    flags:
    - viewer
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  annotateCOMP:
    parameters:
      order: =me.digits or 0
      layerzone: =0 if hasattr(me, 'EncloseOPs') and me.EncloseOPs else 1
    flags:
    - display
    color: [0.45, 0.45, 0.45]
par_templates:
  about:
  - default: '1.0'
    help: Annotate COMP default setup version.
    name: Version
    readOnly: true
    startSection: true
    style: Str
  - help: Click to open help page.
    name: Help
    style: Pulse
  text:
  - default: Annotate
    help: Text in the title bar.
    label: Title Text
    name: Titletext
    startSection: true
    style: Str
  - clampMin: true
    default: 30
    help: Height of the title bar. Title font height adjusts automatically to fill.
    label: Title Height
    name: Titleheight
    normMax: 100.0
    normMin: 5.0
    style: Int
  - default: left
    help: Alignment of title text.
    label: Title Align
    menuLabels:
    - Left
    - Center
    - Right
    menuNames:
    - left
    - center
    - right
    name: Titlealign
    style: Menu
  - help: Text in the body area. Use an expression for newlines etc.
    label: Body Text
    name: Bodytext
    startSection: true
    style: Str
  - clampMin: true
    default: 10
    help: Size of text in the body area.
    label: Body Font Size
    name: Bodyfontsize
    normMax: 100.0
    normMin: 8.0
    style: Int
  - help: Limit the width of the text area.
    label: Limit Body Text Width
    name: Bodylimitwidth
    style: Toggle
  - clampMin: true
    default: 1000
    help: Width limit that will cause wraparound or cut-off. Measured in Panel units.
    label: Max Body Text Width
    name: Bodymaxwidth
    normMax: 2000.0
    normMin: 100.0
    style: Int
  settings:
  - default: comment
    help: 'Switch between Comment, Network Box, and Annotate Modes. '
    menuLabels:
    - Comment
    - Network Box
    - Annotate
    menuNames:
    - comment
    - networkbox
    - annotate
    name: Mode
    style: Menu
  - help: Converts quotes, ellipsis, and dashes to more typographically nice unicode versions.
    label: Smart Quote
    name: Smartquote
    startSection: true
    style: Toggle
  - default: true
    help: Wrap body text when it extends past right bound.
    label: Body Word Wrap
    name: Bodywordwrap
    style: Toggle
  - default: 1
    help: Background color base.
    label: Back Color
    name: Backcolorr
    startSection: true
    style: RGBA
  - clampMax: true
    clampMin: true
    default: 1
    help: Back color alpha.
    label: Back Color Alpha
    name: Backcoloralpha
    style: Float
  - clampMax: true
    clampMin: true
    default: 1
    help: Opacity of the entire Annotate.
    label: Annotate Opacity
    name: Opacity
    style: Float
  op_viewer:
  - help: Turn the visibility of the viewer specified in the OP parameter below on or off.
    label: Viewer Display
    name: Opviewerdisplay
    style: Toggle
  - help: The operator whose viewer is displayed in the Annotate.
    label: OP
    name: Opviewer
    style: OP
  - help: Allow interaction with the OP viewer.
    label: OP Viewer Interactive
    name: Opviewerinteractive
    style: Toggle
  - default: 'False'
    help: Use the Size/Aspect Override to control viewer's size in the background.
    label: Size/Aspect Override
    menuLabels:
    - Natural
    - Specify
    - Auto-Fit
    menuNames:
    - natural
    - specify
    - autofit
    name: Opvieweroversize
    startSection: true
    style: Menu
  - clampMin: true
    default: 800
    help: Diplay viewer as-if it were being displayed at this resolution. This is particularly useful for zooming into operators that don't have a built-in resolution, like CHOPs, SOPs, and DATs.
    label: Size/Aspect
    name: Opviewersize
    normMax: 1000.0
    normMin: 1.0
    style: WH
  - default: 1
    help: Scale the viewer by this factor.
    label: Scale
    name: Opviewerscale
    normMax: 2.0
    startSection: true
    style: Float
  - help: Move the border of the viewer towards left edge of Annotate when negative or towards right edge when positive.
    label: Justify X
    name: Opviewerjustifyx
    normMin: -1.0
    style: Float
  - label: Justify Y
    name: Opviewerjustifyy
    normMin: -1.0
    style: Float
  - help: When True, allow viewer to display in the Annotate title area as well as body.
    label: Cover Body and Title
    name: Opviewerfillbodytitle
    style: Toggle
  - clampMin: true
    default: 1
    help: Zoom the viewer by this scale factor without increasing the size of its display area in the Annotate.
    label: OP Viewer Zoom
    min: 0.001
    name: Opviewerzoom
    normMax: 5.0
    normMin: 1.0
    startSection: true
    style: Float
  - help: Offsets the displayed area within the viewer. Combined with OP Viewer Zoom, this lets you display a specific area of a viewer, such as a CHOP channel or table cell.
    label: OP Viewer Offset
    name: Opvieweroffsetx
    style: XYZW
  - clampMax: true
    clampMin: true
    default: 1
    help: Alpha value of the background area in the OP Viewer.
    label: Fill Alpha
    name: Opviewerfillalpha
    startSection: true
    style: Float
  points:
  - label: Point
    name: pt
    style: Sequence
  - label: Position
    name: Posx
    sequence: pt
    style: XYZW
type: baseCOMP
custom_pars:
  Terrain:
  - name: Height
    style: Float
    startSection: true
    default: 1
    max: 2.0
    clampMin: true
    clampMax: true
    normMax: 2.0
    help: Overall vertical scale of the terrain. Master multiplier on all three noise displacement amplitudes, preserving their 2.9 / 1.0 / 0.25 ratio so peaks and fine relief grow in proportion. 1.0 = the shipped look; 0 = a flat plane; 2 = double-height alpine peaks.
  - name: Ridge
    style: Float
    default: 1.6
    min: 1.0
    max: 3.0
    clampMin: true
    normMin: 1.0
    normMax: 3.0
    help: Ridge sharpness of the mountains. Higher values carve sharper, more knife-edged ridgelines and deeper valleys; lower values give rounder, more eroded hills. 1.6 = the shipped look.
  - name: Warp
    style: Float
    default: 1.2
    max: 2.5
    clampMin: true
    normMax: 2.5
    help: Domain warp amount. Bends the noise domain so ridgelines flow organically instead of aligning to a grid. 0 = regular and repetitive; 1.2 = the shipped look; higher = more chaotic, swirling ridges.
  - name: Detail
    style: Float
    default: 1
    max: 2.0
    clampMin: true
    clampMax: true
    normMax: 2.0
    help: Surface micro-detail and ruggedness, applied cheaply in the shader as normal-map bump strength (no mesh change, so no triangulation cost or facet artifacts). Scales the shipped bump strength of 0.17. 1.0 = the shipped look; 0 = smooth shading; 2 = heavily rugged rock.
  - name: Snowline
    style: Float
    label: Snow Line
    startSection: true
    default: 1.6
    max: 4.0
    clampMin: true
    normMax: 4.0
    help: World-Y elevation where snow begins to cover the rock. Peaks reach roughly 0 to 4 at Height 1.0, so 1.6 sits mid-slope (the shipped look). Lower the value to blanket the terrain in snow; the slider covers 0 to 4, but you can type higher to clear taller peaks at high Height, leaving only bare rock.
  - name: Sundiraz
    style: Float
    label: Sun Azimuth
    startSection: true
    default: -41.6335393366
    min: -180.0
    max: 180.0
    normMin: -180.0
    normMax: 180.0
    help: Compass direction of the sunlight, in degrees in the horizontal plane. 0 points toward +Z (into the scene), +90 toward +X (right), -90 toward -X (left). Combined with Sun Elevation it is converted to a guaranteed unit light direction, so it can never produce a zero-length (NaN) vector. Controls which side of the ridges is lit.
  - name: Sundirel
    style: Float
    label: Sun Elevation
    default: 49.3007177907
    max: 90.0
    clampMin: true
    clampMax: true
    normMax: 90.0
    help: Height of the sun above the horizon, in degrees. 0 = sun on the horizon (long raking shadows, warm grazing light); 90 = directly overhead (flat, even light). Controls shadow length and contrast. The default matches the shipped sun direction.
  - name: Haze
    style: Float
    startSection: true
    default: 1
    max: 2.0
    clampMin: true
    clampMax: true
    normMax: 2.0
    help: Atmospheric haze amount, a multiplier on the computed distance fog and valley mist. 0 = perfectly clear air; 1.0 = the shipped atmosphere; up to 2 = thick, hazy depth that washes out distant peaks. The blend is re-clamped in the shader so it can never fully erase the scene.
  - name: Morphspeed
    style: Float
    label: Morph Speed
    startSection: true
    default: 1
    max: 3.0
    clampMin: true
    normMax: 3.0
    help: Rate at which the terrain shape evolves over time, driving the 4D-noise time coordinate of all three layers while preserving their 0.04 / 0.085 / 0.15 speed ratio. 0 = frozen on a fixed, deterministic frame; 1.0 = the shipped pace; 3 = rapid morphing.
  - name: Seed
    style: Int
    startSection: true
    default: 1
    max: 9999.0
    clampMin: true
    normMax: 100.0
    help: Random seed for the terrain shape. Each value produces a completely different but equally plausible mountain. The three noise layers receive this value plus a fixed per-layer offset (+0, +1, +2) so they stay decorrelated. Default 1 reproduces the shipped seeds 1 / 2 / 3.
flags:
- viewer
color: [0.67, 0.67, 0.67]
operators:
- name: cam
  type: cameraCOMP
  parameters:
    ty: 6.2
    tz: 16
    rx: -13
  flags:
  - display
  position: [500, -330]
  size: [160, 130]
  color: [0.67, 0.67, 0.67]
  children:
  - name: file1
    type: fileinPOP
    parameters:
      file: =app.samplesFolder + '/Geo/defcam.tog'
    flags:
    - display
    - viewer
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
- name: geo
  type: geometryCOMP
  parameters:
    material: glsl_snow
  flags:
  - display
  - render
  size: [160, 130]
  color: [0.67, 0.67, 0.67]
  children:
  - name: null_geo
    type: nullPOP
    flags:
    - render
    position: [1200, 0]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
    inputs:
    - facet_normals
  - name: glsl_terrain
    type: glslPOP
    parameters:
      computedat: glsl_terrain_compute
      outputattrs: '*'
    sequences:
      vec:
      - name: uShape
        type: vec4
        valuex: =parent(2).par.Height.eval()
        valuey: =parent(2).par.Ridge.eval()
        valuez: =parent(2).par.Warp.eval()
        valuew: =parent(2).par.Seed.eval()
      - name: uTime
        type: vec4
        valuex: =absTime.seconds*parent(2).par.Morphspeed.eval()
    position: [400, 0]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
    inputs:
    - grid_terrain
  - name: grid_terrain
    type: gridPOP
    parameters:
      sizex: 20
      sizey: 20
      cols: 128
      rows: 128
      rx: -90
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  - name: facet_normals
    type: facetPOP
    parameters:
      operation: cusp
      angle: 160
    position: [800, 0]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
    inputs:
    - glsl_terrain
  - name: glsl_terrain_info
    type: infoDAT
    parameters:
      op: glsl_terrain
    position: [485, -120]
    dock: glsl_terrain
    dat_read_only: true
  - name: glsl_terrain_compute
    type: textDAT
    position: [335, -120]
    dock: glsl_terrain
    dat_content: |-
      // Ridged multifractal terrain -- GPU geometry generator (replaces fbm noisePOPs).
      // Uniforms auto-declared from the Vectors page: uShape=(Height,Ridge,Warp,Seed), uTime=(time,..).
      // 3D value noise: the 3rd coord = time -> terrain morphs IN PLACE (not a pan); per-octave rate = multi-scale.
      float hash3(vec3 p){ p=fract(p*0.3183099+vec3(0.1,0.2,0.3)); p*=17.0; return fract(p.x*p.y*p.z*(p.x+p.y+p.z)); }
      float vnoise3(vec3 p){
        vec3 i=floor(p), f=fract(p); vec3 u=f*f*(3.0-2.0*f);
        return mix(mix(mix(hash3(i+vec3(0,0,0)),hash3(i+vec3(1,0,0)),u.x),
                       mix(hash3(i+vec3(0,1,0)),hash3(i+vec3(1,1,0)),u.x),u.y),
                   mix(mix(hash3(i+vec3(0,0,1)),hash3(i+vec3(1,0,1)),u.x),
                       mix(hash3(i+vec3(0,1,1)),hash3(i+vec3(1,1,1)),u.x),u.y),u.z);
      }
      float ridged(vec2 p, float sharp, vec2 so, float t){
        float sum=0.0, freq=1.0, amp=0.62, prev=1.0;
        for(int i=0;i<3;i++){
          float tz = t*(0.05+0.045*float(i));            // per-octave morph rate -> earthquake undercurrents
          float n=vnoise3(vec3(p*freq+so, tz));
          n=1.0-abs(2.0*n-1.0);                           // ridge transform -> creases
          n=pow(n,sharp);                                 // sharpen ridgelines
          sum+=n*amp*clamp(prev,0.0,1.0);                 // multifractal: rough peaks, smooth valleys
          prev=n; freq*=2.0; amp*=0.45;
        }
        return sum;
      }
      void main(){
        const uint id=TDIndex();
        if(id>=TDNumElements()) return;
        vec3 pos=TDIn_P(0, id);
        float Height=uShape.x, Ridge=uShape.y, Warp=uShape.z, Seed=uShape.w;
        float t=uTime.x;
        vec2 so=vec2(Seed*19.3, Seed*7.7);               // seed -> a different mountain
        vec2 xz=pos.xz*0.18;                             // domain scale
        vec2 w=vec2(vnoise3(vec3(xz*0.6+so, t*0.03)), vnoise3(vec3(xz*0.6+so+5.2, t*0.03+2.0)));
        xz+=(w-0.5)*Warp;                               // domain warp -> organic, non-griddy
        float h=ridged(xz, Ridge, so, t);
        pos.y+=(h-0.35)*4.6*Height;                     // offset down for rock/snow contrast, then scale
        P[id]=pos;
      }
    dat_content_format: text
- name: out1
  type: outTOP
  parameters:
    label: =me.name
  flags:
  - display
  - viewer
  position: [1700, 0]
  size: [130, 90]
  color: [0.67, 0.67, 0.67]
  inputs:
  - comp_sky
- name: light
  type: lightCOMP
  parameters:
    ty: 10
    tz: 0
    rx: -22
    ry: -45
    lighttype: distant
  flags:
  - display
  position: [500, -580]
  size: [160, 130]
  color: [0.67, 0.67, 0.67]
  children:
  - name: copy1
    type: copyPOP
    parameters:
      ncy: 2
      rx: 90
    flags:
    - viewer
    position: [520, 670]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
    inputs:
    - tube_attenuated
  - name: merge1
    type: mergePOP
    sequences:
      input:
      - {}
      - {}
      - {}
      - {}
      - {}
      - {}
      - {}
    position: [250, 500]
    inputs:
    - rectangle1
    - tube_cone1
    - tube_cone2
    - primitive1
    - line_atten
    - circle_attenStart
    - circle_attenEnd
  - name: merge2
    type: mergePOP
    sequences:
      input:
      - {}
      - {}
    position: [950, 230]
    inputs:
    - connectivity1
    - circle1
  - name: merge3
    type: mergePOP
    sequences:
      input:
      - {}
      - {}
    position: [620, 290]
    inputs:
    - pointgen1
    - transform1
  - name: circle1
    type: circlePOP
    parameters:
      scale: 0.2
    position: [780, 170]
  - name: sphere1
    type: spherePOP
    parameters:
      radx: 0.05
      rady: 0.05
      radz: 0.05
      freq: 3
    flags:
    - viewer
    position: [520, 890]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  - name: pointgen1
    type: pointgeneratorPOP
    parameters:
      shape: circle
      numpoints: 17
      sx: 0.2
      sy: 0.2
      sz: 0.2
    flags:
    - viewer
    position: [270, 290]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  - name: line_atten
    type: linePOP
    parameters:
      divs: 1
      bypass: =not parent().par.attenuated
    sequences:
      pt:
      - posz: =-parent().par.attenuationstart
      - posz: =-parent().par.attenuationend
    custom_pars:
      Points:
        $t: points
    flags:
    - viewer
    position: [0, 230]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  - name: primitive1
    type: primitivePOP
    parameters:
      method: pattern
    sequences:
      prim:
      - type: lines
        pattern: 0 1 0 2 0 3 0 4
      pt:
      - {}
      - posx: =mod.math.sin(mod.math.radians(parent().par.coneangle/2)) + mod.math.sin(mod.math.radians(parent().par.conedelta/2))
        posz: =-op('tube_cone1').par.height
      - posx: =mod.math.sin(-mod.math.radians(parent().par.coneangle/2)) - mod.math.sin(mod.math.radians(parent().par.conedelta/2))
        posz: =0-op('tube_cone1').par.height
      - posy: =-mod.math.sin(-mod.math.radians(parent().par.coneangle/2)) - mod.math.sin(mod.math.radians(parent().par.conedelta/2))
        posz: =-op('tube_cone1').par.height
      - posy: =mod.math.sin(-mod.math.radians(parent().par.coneangle/2)) - mod.math.sin(mod.math.radians(parent().par.conedelta/2))
        posz: =-op('tube_cone1').par.height
    custom_pars:
      Points:
        $t: points
    flags:
    - viewer
    position: [0, 390]
  - name: rectangle1
    type: rectanglePOP
    parameters:
      sizeu: 0.02
      sizev: 0.5
      anchorv: 0
    flags:
    - viewer
    position: [0, 730]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
  - name: transform1
    type: transformPOP
    parameters:
      tz: 0.8
    flags:
    - viewer
    position: [450, 370]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
    inputs:
    - pointgen1
  - name: tube_cone1
    type: tubePOP
    parameters:
      radx: =mod.math.sin(mod.math.radians(parent().par.coneangle/2))
      rady: 0.01
      height: =mod.math.cos(mod.math.radians(parent().par.coneangle/2))
      tz: =-me.par.height*0.5
    position: [0, 620]
  - name: tube_cone2
    type: tubePOP
    parameters:
      radx: =mod.math.sin(mod.math.radians(parent().par.coneangle/2)) + mod.math.sin(mod.math.radians(parent().par.conedelta/2))
      rady: =mod.math.sin(mod.math.radians(parent().par.coneangle/2))
      height: 0
      tz: =-op('tube_cone1').par.height
    position: [0, 500]
  - name: connectivity1
    type: connectivityPOP
    parameters:
      surftype: lines
    flags:
    - viewer
    position: [780, 290]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
    inputs:
    - merge3
  - name: circle_attenEnd
    type: circlePOP
    parameters:
      radx: 0.1
      rady: 0.1
      tz: =-parent().par.attenuationend
      bypass: =not parent().par.attenuated
  - name: primitive_cross
    type: primitivePOP
    parameters:
      method: set
      setprimtype: lines
    sequences:
      pt:
      - posx: -0.2
      - posx: 0.2
      - posy: -0.2
      - posy: 0.2
      - posz: -0.2
      - posz: 0.2
    custom_pars:
      Points:
        $t: points
    flags:
    - display
    - render
    - viewer
    position: [520, 1000]
  - name: tube_attenuated
    type: tubePOP
    parameters:
      surftype: rows
      radx: =parent().par.attenuationend
      rady: =parent().par.attenuationstart
      height: 0
      bypass: =not parent().par.attenuated
    position: [368, 673]
  - name: merge_pointLight
    type: mergePOP
    sequences:
      input:
      - {}
      - {}
      - {}
      - {}
    position: [760, 820]
    inputs:
    - sphere1
    - primitive_quadrants
    - copy1
  - name: switch_lightType
    type: switchPOP
    parameters:
      index: =parent().par.lighttype.menuIndex
    flags:
    - display
    - render
    - viewer
    position: [1140, 500]
    size: [130, 90]
    color: [0.67, 0.67, 0.67]
    inputs:
    - merge_pointLight
    - merge1
    - merge2
  - name: circle_attenStart
    type: circlePOP
    parameters:
      radx: 0.1
      rady: 0.1
      tz: =-parent().par.attenuationstart
      bypass: =not parent().par.attenuated
    position: [0, 120]
  - name: primitive_quadrants
    type: primitivePOP
    parameters:
      method: set
      setprimtype: lines
    sequences:
      pt:
      - posx: 0.07
        posy: 0.07
        posz: 0.07
      - posx: -0.07
        posy: -0.07
        posz: -0.07
      - posx: -0.07
        posy: 0.07
        posz: 0.07
      - posx: 0.07
        posy: -0.07
        posz: -0.07
      - posx: 0.07
        posy: 0.07
        posz: -0.07
      - posx: -0.07
        posy: -0.07
        posz: 0.07
      - posx: -0.07
        posy: 0.07
        posz: -0.07
      - posx: 0.07
        posy: -0.07
        posz: 0.07
    custom_pars:
      Points:
        $t: points
    flags:
    - viewer
    position: [520, 780]
- name: comp_sky
  type: compositeTOP
  parameters:
    operand: over
  position: [1400, 0]
  size: [130, 90]
  color: [0.67, 0.67, 0.67]
  inputs:
  - render_scene
  - glsl_sky
- name: glsl_sky
  type: glslTOP
  parameters:
    pixeldat: glsl_sky_pixel
    computedat: glsl_sky_compute
    outputresolution: custom
    resolutionw: 1024
    resolutionh: 439
  flags:
  - viewer
  position: [900, -330]
  size: [130, 90]
  color: [0.67, 0.67, 0.67]
- name: annotate1
  type: annotateCOMP
  sequences:
    ext:
    - object: op.TDAnnotate.mod.AnnotateExt.AnnotateExt(me)
      promote: true
  custom_pars:
    Text:
      $t: text
      Titletext: Terrain + Snow Shader
      Bodytext: Ridged-multifractal terrain in a GLSL POP (inside geo), shaded by a snow/rock GLSL MAT. Height / Ridge / Warp / Seed / Morph Speed drive the POP; Detail / Snow Line / Sun / Haze drive the material.
    Settings:
      $t: settings
      Mode: annotate
      Backcolorr:
      - '=me.color[0] # node color [r]'
      - =me.color[1]
      - '=me.color[2] '
    OP Viewer:
      $t: op_viewer
      Opvieweroversize: natural
    About:
      $t: about
  position: [-220, -520]
  size: [570, 820]
  palette_clone: true
- name: annotate2
  type: annotateCOMP
  sequences:
    ext:
    - object: op.TDAnnotate.mod.AnnotateExt.AnnotateExt(me)
      promote: true
  custom_pars:
    Text:
      $t: text
      Titletext: 3D Scene + Sky
      Bodytext: A camera and a distant sun light render the displaced geometry (render_scene). A bright procedural sky is generated separately in glsl_sky.
    Settings:
      $t: settings
      Mode: annotate
      Backcolorr:
      - '=me.color[0] # node color [r]'
      - =me.color[1]
      - '=me.color[2] '
    OP Viewer:
      $t: op_viewer
      Opvieweroversize: natural
    About:
      $t: about
  position: [430, -650]
  size: [820, 910]
  palette_clone: true
- name: annotate3
  type: annotateCOMP
  sequences:
    ext:
    - object: op.TDAnnotate.mod.AnnotateExt.AnnotateExt(me)
      promote: true
  custom_pars:
    Text:
      $t: text
      Titletext: Composite + Output
      Bodytext: The sky is composited under the rendered terrain (comp_sky) and sent to out1 -- the specimen's output.
    Settings:
      $t: settings
      Mode: annotate
      Backcolorr:
      - '=me.color[0] # node color [r]'
      - =me.color[1]
      - '=me.color[2] '
    OP Viewer:
      $t: op_viewer
      Opvieweroversize: natural
    About:
      $t: about
  position: [1330, -70]
  size: [570, 370]
  palette_clone: true
- name: glsl_snow
  type: glslMAT
  parameters:
    vdat: glsl_snow_vertex
    pdat: glsl_snow_pixel
    skelrootpath: =parent()
  sequences:
    mattr:
    - comps: 2
      cols: 2
    vec:
    - name: uTerrain
      valuex: =parent().par.Snowline.eval()
      valuey: =0.17*parent().par.Detail.eval()
      valuez: =parent().par.Haze.eval()
      valuew: 0
    - name: uSunDir
      valuex: =math.cos(math.radians(parent().par.Sundirel.eval()))*math.sin(math.radians(parent().par.Sundiraz.eval()))
      valuey: =math.sin(math.radians(parent().par.Sundirel.eval()))
      valuez: =math.cos(math.radians(parent().par.Sundirel.eval()))*math.cos(math.radians(parent().par.Sundiraz.eval()))
      valuew: 0
  position: [0, -330]
  size: [130, 90]
  color: [0.67, 0.67, 0.67]
- name: render_scene
  type: renderTOP
  parameters:
    camera: cam
    geometry: geo
    lights: light
    antialias: aa2
    resolutionw: 1024
    resolutionh: 439
  flags:
  - viewer
  position: [500, 0]
  size: [130, 90]
  color: [0.67, 0.67, 0.67]
- name: glsl_sky_info
  type: infoDAT
  parameters:
    op: glsl_sky
  position: [900, -450]
  dock: glsl_sky
  dat_read_only: true
- name: glsl_sky_pixel
  type: textDAT
  parameters:
    extension: frag
  position: [1050, -450]
  dock: glsl_sky
  dat_content: |-
    out vec4 fragColor;
    float h2(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453); }
    float n2(vec2 p){ vec2 i=floor(p),f=fract(p); f=f*f*(3.0-2.0*f);
      return mix(mix(h2(i),h2(i+vec2(1,0)),f.x),mix(h2(i+vec2(0,1)),h2(i+vec2(1,1)),f.x),f.y); }
    float fbm2(vec2 p){ float v=0.0,a=0.5; for(int i=0;i<4;i++){v+=a*n2(p); p*=2.0; a*=0.5;} return v; }
    void main(){
        vec2 uv=vUV.st; float y=uv.t;
        vec3 zenith=vec3(0.17,0.42,0.83); vec3 horizon=vec3(0.74,0.84,0.93);
        vec3 col=mix(horizon,zenith,smoothstep(0.0,0.95,y));
        float cl=fbm2(vec2(uv.x*3.5,uv.y*6.0));
        col=mix(col,vec3(0.97,0.98,1.0),smoothstep(0.6,0.85,cl)*0.45);
        fragColor=vec4(col,1.0);
    }
  dat_content_format: text
- name: glsl_snow_info
  type: infoDAT
  parameters:
    op: glsl_snow
  position: [-150, -450]
  dock: glsl_snow
  dat_read_only: true
- name: glsl_snow_pixel
  type: textDAT
  parameters:
    extension: frag
  position: [0, -450]
  dock: glsl_snow
  dat_content: |-
    uniform vec4 uTerrain;   // x = snow line base (was 1.6), y = bump strength (was 0.17), z = haze multiplier, w = unused
    uniform vec4 uSunDir;    // xyz = sun light direction (unit, from az/el binding), w = unused
    in vec3 iWorldPos;
    in vec3 iWorldNorm;
    out vec4 oFragColor;
    float hash(vec3 p){ return fract(sin(dot(p,vec3(127.1,311.7,74.7)))*43758.5453); }
    float vnoise(vec3 p){ vec3 i=floor(p),f=fract(p); f=f*f*(3.0-2.0*f);
      return mix(mix(mix(hash(i+vec3(0,0,0)),hash(i+vec3(1,0,0)),f.x),mix(hash(i+vec3(0,1,0)),hash(i+vec3(1,1,0)),f.x),f.y),
                 mix(mix(hash(i+vec3(0,0,1)),hash(i+vec3(1,0,1)),f.x),mix(hash(i+vec3(0,1,1)),hash(i+vec3(1,1,1)),f.x),f.y),f.z); }
    float fbm(vec3 p){ float v=0.0,a=0.5; for(int i=0;i<4;i++){v+=a*vnoise(p); p=p*2.03+vec3(1.7); a*=0.5;} return v; }
    float fbm3(vec3 p){ float v=0.0,a=0.5; for(int i=0;i<3;i++){v+=a*vnoise(p); p=p*2.05+vec3(1.7); a*=0.5;} return v; }
    vec3 bumpN(vec3 P,vec3 N,float fr,float st){ float e=0.03;
      float h=fbm3(P*fr),hx=fbm3((P+vec3(e,0,0))*fr),hz=fbm3((P+vec3(0,0,e))*fr);
      return normalize(N+vec3((h-hx),0.0,(h-hz))/e*st); }
    void main(){
        TDCheckDiscard();
        vec3 P=iWorldPos; vec3 Ng=normalize(iWorldNorm);
        float slopeG=clamp(Ng.y,0.0,1.0);
        float snowLine=uTerrain.x+(fbm(P*0.4)-0.5)*1.1;
        float snowMask=clamp(smoothstep(snowLine-0.5,snowLine+0.5,P.y),0.0,1.0);
        vec3 N=bumpN(P,Ng,9.0,uTerrain.y*(1.0-0.85*snowMask));
        float slope=clamp(N.y,0.0,1.0);
        float c1=fbm(P*1.3), c2=fbm(P*3.2+5.0), c3=fbm(P*0.7-3.0); float fine=fbm(P*4.0);
        vec3 rock=vec3(0.46,0.34,0.24);
        rock=mix(rock,vec3(0.34,0.37,0.40),smoothstep(0.35,0.75,c1));
        rock=mix(rock,vec3(0.44,0.22,0.16),smoothstep(0.55,0.85,c2)*0.6);
        rock=mix(rock,vec3(0.28,0.34,0.20),smoothstep(0.58,0.88,c3)*0.4);
        rock*=0.72+0.28*fine;
        vec3 snow=vec3(0.88,0.90,0.95);
        vec3 albedo=mix(rock,snow,snowMask);
        vec3 L=normalize(uSunDir.xyz);
        float ndl=max(dot(N,L),0.0);
        vec3 sun=vec3(1.55,1.5,1.35)*ndl*(1.0-0.55*snowMask);  // expose snow below clipping, keep shading
        vec3 sky=vec3(0.40,0.52,0.74)*(0.42+0.58*slope);
        vec3 col=albedo*(sun+sky);
        col+=pow(ndl,60.0)*snowMask*0.18;
        col=(col-0.45)*1.12+0.45;
        float dist=length(P-vec3(0.0,6.2,16.0));
        float depth=clamp((dist-9.0)/19.0,0.0,1.0);                                    // 0 = foreground (clear), 1 = far
        float distFog=pow(depth,1.7)*0.82;                                             // power curve: clear fg, builds with depth
        float valleyMist=exp(-max(P.y-0.2,0.0)*0.7)*0.18*smoothstep(0.25,0.75,depth);  // mist only in distant valleys
        float atmo=clamp((distFog+valleyMist)*uTerrain.z,0.0,0.88);
        col=mix(col,vec3(0.66,0.78,0.92),atmo);
        oFragColor=TDOutputSwizzle(vec4(clamp(col,0.0,2.0),1.0));
    }
  dat_content_format: text
- name: glsl_sky_compute
  type: textDAT
  position: [750, -450]
  dock: glsl_sky
- name: glsl_snow_vertex
  type: textDAT
  parameters:
    extension: vert
  position: [150, -450]
  dock: glsl_snow
  dat_content: |-
    out vec3 iWorldPos;
    out vec3 iWorldNorm;
    void main(){
        vec3 pos = TDPos();
        vec4 worldSpacePos = TDDeform(pos);
        iWorldPos = worldSpacePos.xyz;
        iWorldNorm = TDDeformNorm(TDNormal());
        gl_Position = TDWorldToProj(worldSpacePos);
    }
  dat_content_format: text
annotations:
- name: annotate1
  mode: annotate
  title: Terrain + Snow Shader
  text: Ridged-multifractal terrain in a GLSL POP (inside geo), shaded by a snow/rock GLSL MAT. Height / Ridge / Warp / Seed / Morph Speed drive the POP; Detail / Snow Line / Sun / Haze drive the material.
  position: [-220, -520]
  size: [570, 820]
  color: [0.45, 0.45, 0.45]
- name: annotate2
  mode: annotate
  title: 3D Scene + Sky
  text: A camera and a distant sun light render the displaced geometry (render_scene). A bright procedural sky is generated separately in glsl_sky.
  position: [430, -650]
  size: [820, 910]
  color: [0.45, 0.45, 0.45]
- name: annotate3
  mode: annotate
  title: Composite + Output
  text: The sky is composited under the rendered terrain (comp_sky) and sent to out1 -- the specimen's output.
  position: [1330, -70]
  size: [570, 370]
  color: [0.45, 0.45, 0.45]