Skip to content

Latest commit

 

History

History
954 lines (867 loc) · 37.2 KB

webgl.org

File metadata and controls

954 lines (867 loc) · 37.2 KB

This file is archived and only kept for reference - DO NOT edit

Contents

WebGL examples & test scenes

Usage

These examples can be built by running the following commands in the geom root directory:

./tangle-all.sh
cd babel
lein do clean, cljsbuild once prod
open index.html

Then in the browser, using the JS console, the demos can be individually launched like:

thi.ng.geom.examples.gl.ex01.demo()

Templates for namespace setup

Shared imports

(:require-macros
 [thi.ng.math.macros :as mm])
[thi.ng.math.core :as m :refer [PI HALF_PI TWO_PI]]
[thi.ng.color.core :as col]
[thi.ng.typedarrays.core :as arrays]
[thi.ng.geom.gl.core :as gl]
[thi.ng.geom.gl.webgl.constants :as glc]
[thi.ng.geom.gl.webgl.animator :as anim]
[thi.ng.geom.gl.buffers :as buf]
[thi.ng.geom.gl.shaders :as sh]
[thi.ng.geom.gl.utils :as glu]
[thi.ng.geom.gl.glmesh :as glm]
[thi.ng.geom.gl.camera :as cam]
[thi.ng.geom.core :as g]
[thi.ng.geom.vector :as v :refer [vec2 vec3]]
[thi.ng.geom.matrix :as mat :refer [M44]]
[thi.ng.geom.aabb :as a]
[thi.ng.geom.attribs :as attr]
[thi.ng.glsl.core :as glsl :include-macros true]
(defn init-stats
  []
  (let [stats (js/Stats.)
        sdom  (.call (aget stats "getDomElement") stats)]
    (.appendChild (.-body js/document) sdom)
    (.setAttribute sdom "class" "stats")
    stats))

(defn update-stats
 [stats]
 (.call (aget stats "update") stats))

Example 1: 2D polygons

This demo utilizes the basic color shader to draw two filled, rotating 2d polygons. The shader itself is generated based on given parameters and the example shows some alternatives:

  • shader1 is requesting to specify the color as a uniform value
  • shader2 is requesting to specify the color as a vertex attribute and we generate the required attribute buffer in the call to as-gl-buffer-spec.

If we’re only after displaying a model with a single color, specifying it as shader uniform is much more efficient. However, having an attribute buffer allows us to specify colors per vertex and hence provides more flexibility.

For both cases we set the :3d option to false in order to save on bandwidth since our polygons are only 2d entities anyhow. Another implication of this is that we don’t need to specify a view matrix, which has to be given for the 3d case though. Please see the above link for more discussion about these options.

(ns thi.ng.geom.examples.gl.ex01
  <<require-macros>>
  (:require
   <<default-requires>>
   [thi.ng.geom.gl.shaders.basic :as basic]
   [thi.ng.geom.circle :as c]
   [thi.ng.geom.polygon :as poly]))

<<stats>>

(defn ^:export demo
  []
  (enable-console-print!)
  (let [gl        (gl/gl-context "main")
        view-rect (gl/get-viewport-rect gl)
        shader1   (sh/make-shader-from-spec gl (basic/make-shader-spec-2d false))
        shader2   (sh/make-shader-from-spec gl (basic/make-shader-spec-2d true))
        teeth     20
        model     (-> (poly/cog 0.5 teeth [0.9 1 1 0.9])
                      (gl/as-gl-buffer-spec {:normals false :fixed-color [1 0 0 1]})
                      (gl/make-buffers-in-spec gl glc/static-draw)
                      (assoc-in [:uniforms :proj] (gl/ortho view-rect))
                      (time))
        stats     (init-stats)]
    (anim/animate
     (fn [t frame]
       (gl/set-viewport gl view-rect)
       (gl/clear-color-and-depth-buffer gl 1 0.98 0.95 1 1)
       ;; draw left polygon using color uniform (that's why we need to remove color attrib)
       (gl/draw-with-shader
        gl (-> model
               (assoc :shader  shader1)
               (update-in [:attribs] dissoc :color)
               (update-in [:uniforms] merge
                          {:model (-> M44 (g/translate (vec3 -0.48 0 0)) (g/rotate t))
                           :color [0 1 1 1]})))
       ;; draw right polygon using color attribs
       (gl/draw-with-shader
        gl (-> model
               (assoc :shader shader2)
               (assoc-in [:uniforms :model]
                         (-> M44 (g/translate (vec3 0.48 0 0)) (g/rotate (- (+ t (/ HALF_PI teeth))))))))
       (update-stats stats)
       true))))

Example 2: Colored mesh

This example demonstrates the basic principle of displaying a mesh with vertex attributes. We define a box with different colors for each of its 6 sides. The colors must be specified as RGBA using the thi.ng/color library.

(ns thi.ng.geom.examples.gl.ex02
  <<require-macros>>
  (:require
   <<default-requires>>
   [thi.ng.geom.gl.shaders.basic :as basic]))

(defn ^:export demo
  []
  (let [gl        (gl/gl-context "main")
        view-rect (gl/get-viewport-rect gl)
        model     (-> (a/aabb 0.8)
                      (g/center)
                      (g/as-mesh
                       {:mesh    (glm/indexed-gl-mesh 12 #{:col})
                        :attribs {:col (->> [[1 0 0] [0 1 0] [0 0 1] [0 1 1] [1 0 1] [1 1 0]]
                                            (map col/rgba)
                                            (attr/const-face-attribs))}})
                      (gl/as-gl-buffer-spec {})
                      (cam/apply (cam/perspective-camera {:aspect view-rect}))
                      (assoc :shader (sh/make-shader-from-spec gl (basic/make-shader-spec-3d true)))
                      (gl/make-buffers-in-spec gl glc/static-draw))]
    (anim/animate
     (fn [t frame]
       (doto gl
         (gl/set-viewport view-rect)
         (gl/clear-color-and-depth-buffer col/WHITE 1)
         (gl/enable glc/depth-test)
         (gl/draw-with-shader
          (assoc-in model [:uniforms :model] (-> M44 (g/rotate-x t) (g/rotate-y (* t 2))))))
       true))))

Same example as above, only this time using basic lambert shading…

(ns thi.ng.geom.examples.gl.ex02a
  <<require-macros>>
  (:require
   <<default-requires>>
   [thi.ng.geom.gl.shaders.lambert :as lambert]))

(defn ^:export demo
  []
  (let [gl        (gl/gl-context "main")
        view-rect (gl/get-viewport-rect gl)
        model     (-> (a/aabb 0.8)
                      (g/center)
                      (g/as-mesh
                       {:mesh    (glm/indexed-gl-mesh 12 #{:col :fnorm})
                        :attribs {:col (->> [[1 0 0] [0 1 0] [0 0 1] [0 1 1] [1 0 1] [1 1 0]]
                                            (map col/rgba)
                                            (attr/const-face-attribs))}})
                      (gl/as-gl-buffer-spec {})
                      (cam/apply (cam/perspective-camera {:aspect view-rect}))
                      (assoc :shader (sh/make-shader-from-spec gl lambert/shader-spec-attrib))
                      (gl/make-buffers-in-spec gl glc/static-draw))]
    (anim/animate
     (fn [t frame]
       (doto gl
         (gl/set-viewport view-rect)
         (gl/clear-color-and-depth-buffer col/WHITE 1)
         (gl/draw-with-shader
          (assoc-in model [:uniforms :model] (-> M44 (g/rotate-x t) (g/rotate-y (* t 2))))))
       true))))

Example 3: Extruded 2D polygons with Blinn-Phong shading

Using the same model setup as in example #1, this time we extrude the polygon and render the resulting 3D mesh. To better appreciate the mesh structure, we make use of the Phong shader and see how to combine transformation matrices: Each cog rotates around its own axis, but is then also rotated around the global Y axis. This is achieved with simple matrix multiplication (rot is the global rotation / transformation).

(ns thi.ng.geom.examples.gl.ex03
  <<require-macros>>
  (:require
   <<default-requires>>
   [thi.ng.geom.gl.shaders.phong :as phong]
   [thi.ng.geom.circle :as c]
   [thi.ng.geom.polygon :as poly]))

<<stats>>

(defn ^:export demo
  []
  (enable-console-print!)
  (let [gl        (gl/gl-context "main")
        view-rect (gl/get-viewport-rect gl)
        teeth     20
        model     (-> (poly/cog 0.5 teeth [0.9 1 1 0.9])
                      (g/extrude-shell {:mesh    (glm/indexed-gl-mesh 1000 #{:fnorm})
                                        :depth   0.1
                                        :inset   0.025
                                        :wall    0.015
                                        :bottom? true})
                      (gl/as-gl-buffer-spec {})
                      (cam/apply (cam/perspective-camera {:aspect view-rect}))
                      (assoc :shader (sh/make-shader-from-spec gl phong/shader-spec))
                      (update :uniforms merge
                              {:lightPos      (vec3 0.1 0 1)
                               :ambientCol    0x0e1a4c
                               :diffuseCol    0xff3310
                               :specularCol   0x99ffff
                               :shininess     100
                               :wrap          0
                               :useBlinnPhong true})
                      (gl/make-buffers-in-spec gl glc/static-draw)
                      (time))
        stats     (init-stats)]
    (anim/animate
     (fn [t frame]
       (let [rot (g/rotate-y M44 (* t 0.5))
             tx1 (m/* rot (-> M44
                              (g/translate (vec3 -0.46 0 0))
                              (g/rotate-y 0.3)
                              (g/rotate-z t)))
             tx2 (m/* rot (-> M44
                              (g/translate (vec3 0.46 0 0))
                              (g/rotate-y -0.3)
                              (g/rotate-z (- (+ t (/ HALF_PI teeth))))))]
         (doto gl
           (gl/set-viewport view-rect)
           (gl/clear-color-and-depth-buffer 1 0.98 0.95 1 1)
           (gl/draw-with-shader (assoc-in model [:uniforms :model] tx1))
           (gl/draw-with-shader
            (-> model
                (assoc-in [:uniforms :model] tx2)
                (assoc-in [:uniforms :diffuseCol] 0x33ff80))))
         (update-stats stats)
         true)))))

Example 4: Unproject & scene raycasting

Live version: http://demo.thi.ng/geom/webgl/unproject/

(ns thi.ng.geom.examples.gl.ex04
  <<require-macros>>
  (:require
   <<default-requires>>
   [thi.ng.geom.plane :as pl]
   [thi.ng.geom.gl.shaders.phong :as phong]))

(enable-console-print!)

(defn raycast
  [p eye ground back]
  (let [dir (m/- p eye)
        i1  (:p (g/intersect-ray ground eye dir))
        i2  (:p (g/intersect-ray back eye dir))]
    (if (< (g/dist-squared eye i1) (g/dist-squared eye i2)) i1 i2)))

(defn ^:export demo
  []
  (let [gl         (gl/gl-context "main")
        view-rect  (gl/get-viewport-rect gl)
        shader     (sh/make-shader-from-spec gl phong/shader-spec)
        cam        (cam/perspective-camera
                    {:eye (vec3 1 2 6)
                     :target (vec3 0 0.6 0)
                     :aspect view-rect
                     :far 10})
        size       3
        ground-y   -0.55
        uniforms   {:model     M44
                    :shininess 1000
                    :lightPos  (vec3 -1 2 0)}
        box        (-> (a/aabb 1)
                       (g/center)
                       (g/as-mesh {:mesh (glm/indexed-gl-mesh 12 #{:fnorm})})
                       (gl/as-gl-buffer-spec {})
                       (gl/make-buffers-in-spec gl glc/static-draw)
                       (assoc :shader shader :uniforms (assoc uniforms :diffuseCol [1 0 1])))
        ground     (pl/plane-with-point (vec3 0 ground-y 0) v/V3Y)
        back       (pl/plane-with-point (vec3 0 0 (* -0.5 size)) v/V3Z)
        planes     (-> (g/as-mesh back {:mesh (glm/indexed-gl-mesh 4 #{:fnorm}) :size size})
                       (g/translate (vec3 0 (+ (* 0.5 size) ground-y) 0))
                       (g/into (g/as-mesh ground {:size size}))
                       (gl/as-gl-buffer-spec {})
                       (gl/make-buffers-in-spec gl glc/static-draw)
                       (assoc :shader shader :uniforms uniforms))
        state      (volatile! {:mpos (g/centroid view-rect) :update-ray true})
        update-pos #(vswap! state assoc
                            :mpos (vec2 (.-clientX %) (.-clientY %))
                            :update-ray true)]
    (.addEventListener js/window "mousemove" update-pos)
    (.addEventListener js/window "touchmove" #(do (.preventDefault %) (update-pos (aget (.-touches %) 0))))
    (anim/animate
     (fn [t frame]
       (let [cam  (cam/set-view cam {:eye #(g/rotate-y % (Math/sin t))})
             isec (if (:update-ray @state)
                    (let [p (-> (vec3 (:mpos @state) 0)
                                (mat/unproject-point (m/invert (m/* (:proj cam) (:view cam))) view-rect)
                                (raycast (:eye cam) ground back))]
                      (vswap! state assoc :isec p :update-ray false) p)
                    (:isec @state))]
         (doto gl
           (gl/set-viewport view-rect)
           (gl/clear-color-and-depth-buffer 0.52 0.5 0.5 1 1)
           (gl/draw-with-shader (cam/apply planes cam))
           (gl/draw-with-shader
            (-> box
                (cam/apply cam)
                (assoc-in [:uniforms :model] (g/translate M44 isec))))))
       true))
    state))

Example 5: Texture mapping

Live version: http://demo.thi.ng/geom/webgl/texcube/

(ns thi.ng.geom.examples.gl.ex05
  <<require-macros>>
  (:require
   <<default-requires>>))

(enable-console-print!)

(def shader-spec
  {:vs (glsl/minified
        "void main() {
           vUV = uv;
           gl_Position = proj * view * model * vec4(position, 1.0);
         }")
   :fs (glsl/minified
        "void main() {
           gl_FragColor = texture2D(tex, vUV);
         }")
   :uniforms {:model    [:mat4 M44]
              :view     :mat4
              :proj     :mat4
              :tex      :sampler2D}
   :attribs  {:position :vec3
              :uv       :vec2}
   :varying  {:vUV      :vec2}
   :state    {:depth-test true}})

(defn ^:export demo
  []
  (let [gl        (gl/gl-context "main")
        view-rect (gl/get-viewport-rect gl)
        model     (-> (a/aabb 1)
                      (g/center)
                      (g/as-mesh
                       {:mesh    (glm/indexed-gl-mesh 12 #{:uv})
                        :attribs {:uv (attr/face-attribs (attr/uv-cube-map-v 256 false))}})
                      (gl/as-gl-buffer-spec {})
                      (cam/apply (cam/perspective-camera {:eye (vec3 0 0 0.5) :fov 90 :aspect view-rect}))
                      (assoc :shader (sh/make-shader-from-spec gl shader-spec))
                      (gl/make-buffers-in-spec gl glc/static-draw))
        tex-ready (volatile! false)
        tex       (buf/load-texture
                   gl {:callback (fn [tex img] (vreset! tex-ready true))
                       :src      "../assets/cubev.png"
                       :flip     false})]
    (anim/animate
     (fn [t frame]
       (when @tex-ready
         (gl/bind tex 0)
         (doto gl
           (gl/set-viewport view-rect)
           (gl/clear-color-and-depth-buffer col/WHITE 1)
           (gl/draw-with-shader
            (assoc-in model [:uniforms :model]
                      (-> M44 (g/rotate-x PI) (g/rotate-y (* t 2)))))))
       true))))

Live version: http://demo.thi.ng/geom/webgl/ptf-knot/ & http://demo.thi.ng/geom/webgl/ptf-knot3/

(ns thi.ng.geom.examples.gl.ex05a
  <<require-macros>>
  (:require
   <<default-requires>>
   [thi.ng.geom.circle :as c]
   [thi.ng.geom.ptf :as ptf]
   [thi.ng.glsl.vertex :as vertex]
   [thi.ng.glsl.lighting :as light]
   [thi.ng.glsl.fog :as fog]
   [thi.ng.color.gradients :as grad]
   ))

(enable-console-print!)

(def shader-spec
  {:vs (->> "void main() {
                vUV = uv + vec2(0, time * 0.025);
                vPos = (view * model * vec4(position, 1.0)).xyz;
                vNormal = surfaceNormal(normal, normalMat);
                vLightDir = (view * vec4(lightPos, 1.0)).xyz - vPos;
                gl_Position = proj * vec4(vPos, 1.0);
              }"
            (glsl/glsl-spec [vertex/surface-normal])
            (glsl/assemble))
   :fs (->> "void main() {
                vec3 n = normalize(vNormal);
                vec3 v = normalize(-vPos);
                vec3 l = normalize(vLightDir);
                float NdotL = max(0.0, dot(n, l));
                vec3 specular = Ks * beckmannSpecular(l, v, n, m);
                vec3 att = lightCol / pow(length(vLightDir), lightAtt);
                vec3 diff = texture2D(tex, vUV).xyz;
                vec3 col = att * NdotL * ((1.0 - s) * diff + s * specular) + Ka * diff;
                float fog = fogLinear(length(vPos), 1.0, 7.5);
                col = mix(col, Kf, fog);
                gl_FragColor = vec4(col, 1.0);
              }"
            (glsl/glsl-spec [fog/fog-linear light/beckmann-specular])
            (glsl/assemble))
   :uniforms {:model     :mat4
              :view      :mat4
              :proj      :mat4
              :normalMat :mat4
              :tex       :sampler2D
              :Ks        [:vec3 [1 1 1]]
              :Ka        [:vec3 [0.0 0.0 0.3]]
              :Kf        [:vec3 [0.0 0.0 0.1]]
              :m         [:float 0.1]
              :s         [:float 0.9]
              :lightCol  [:vec3 [200 80 40]]
              :lightPos  [:vec3 [0 0 5]]
              :lightAtt  [:float 3.0]
              :time      :float}
   :attribs  {:position :vec3
              :normal   :vec3
              :uv       :vec2}
   :varying  {:vUV      :vec2
              :vPos     :vec3
              :vNormal  :vec3
              :vLightDir :vec3}
   :state    {:depth-test true}})

(defn cinquefoil
  [t]
  (let [t  (* t m/TWO_PI)
        pt (* 2.0 t)
        qt (* 5.0 t)
        qc (+ 3.0 (Math/cos qt))]
    (v/vec3 (* qc (Math/cos pt)) (* qc (Math/sin pt)) (Math/sin qt))))

(defn knot-simple
  []
  (-> (mapv cinquefoil (butlast (m/norm-range 400)))
      (ptf/sweep-mesh
       (g/vertices (c/circle 0.5) 20)
       {:mesh    (glm/gl-mesh 16800 #{:fnorm :uv})
        :attribs {:uv attr/uv-tube}
        :align?  true
        :loop?   true})))

(defn knot-nested
  [nums numt]
  (-> (mapv cinquefoil (m/norm-range 400))
      (ptf/compute-frames)
      (ptf/align-frames)
      (ptf/sweep-strand-mesh
       #(+ 0.5 (* 0.5 (Math/sin (* 8 TWO_PI (/ % 400.0)))))
       8 7 (g/vertices (c/circle 0.05) 8)
       {:mesh    (glm/gl-mesh 65536 #{:fnorm :uv})
        :attribs {:uv attr/uv-tube}
        :align?  true})))

(defn gradient-texture
  [gl w h opts]
  (let [canv (.createElement js/document "canvas")
        ctx  (.getContext canv "2d")
        cols (grad/cosine-gradient h (:rainbow1 grad/cosine-schemes))]
    (set! (.-width canv) w)
    (set! (.-height canv) h)
    (set! (.-strokeStyle ctx) "none")
    (loop [y 0, cols cols]
      (if cols
        (let [c (first cols)
              c (if (< (mod y 16) 8)
                  (col/adjust-brightness c -0.75)
                  c)]
          (set! (.-fillStyle ctx) @(col/as-css c))
          (.fillRect ctx 0 y w 1)
          (recur (inc y) (next cols)))
        (buf/make-canvas-texture gl canv opts)))))

(defn ^:export demo
  []
  (let [gl        (gl/gl-context "main")
        view-rect (gl/get-viewport-rect gl)
        model     (-> (knot-simple)
                      #_(knot-nested 8 7)
                      (gl/as-gl-buffer-spec {})
                      (cam/apply (cam/perspective-camera {:eye (vec3 0 0 5) :fov 90 :aspect view-rect}))
                      (assoc :shader (sh/make-shader-from-spec gl shader-spec))
                      (gl/make-buffers-in-spec gl glc/static-draw)
                      (time))
        tex       (gradient-texture gl 4 1024 {:wrap [glc/clamp-to-edge glc/repeat]})]
    (anim/animate
     (fn [t frame]
       (gl/bind tex 0)
       (doto gl
         (gl/set-viewport view-rect)
         (gl/clear-color-and-depth-buffer 0.0 0.0 0.1 1 1)
         (gl/draw-with-shader
          (-> model
              (update :uniforms assoc
                      :time t
                      :m (+ 0.21 (* 0.2 (Math/sin (* t 0.5))))
                      :model (-> M44 (g/rotate-x (* t 0.36)) (g/rotate-y t)))
              (gl/inject-normal-matrix :model :view :normalMat))))
       true))))

Example 5b: Multiple textures

Live version: http://demo.thi.ng/geom/webgl/multitex/

(ns thi.ng.geom.examples.gl.ex05b
  <<require-macros>>
  (:require
   <<default-requires>>))

(enable-console-print!)

(def shader-spec
  {:vs (glsl/minified
        "void main() {
           vUV = uv;
           gl_Position = proj * view * model * vec4(position, 1.0);
         }")
   :fs (glsl/minified
        "void main() {
           gl_FragColor = mix(texture2D(tex1, vUV), texture2D(tex2, vUV), fade);
         }")
   :uniforms {:model    [:mat4 M44]
              :view     :mat4
              :proj     :mat4
              :tex1     [:sampler2D 0] ;; bound to tex unit #0
              :tex2     [:sampler2D 1] ;; bound to tex unit #1
              :fade     :float}
   :attribs  {:position :vec3
              :uv       :vec2}
   :varying  {:vUV      :vec2}
   :state    {:depth-test true}})

(defn ^:export demo
  []
  (let [gl        (gl/gl-context "main")
        view-rect (gl/get-viewport-rect gl)
        model     (-> (a/aabb 1)
                      (g/center)
                      (g/as-mesh
                       {:mesh    (glm/indexed-gl-mesh 12 #{:uv})
                        :attribs {:uv (attr/face-attribs (attr/uv-cube-map-v 256 false))}})
                      (gl/as-gl-buffer-spec {})
                      (cam/apply (cam/perspective-camera {:eye (vec3 0 0 0.5) :fov 60 :aspect view-rect}))
                      (assoc :shader (sh/make-shader-from-spec gl shader-spec))
                      (gl/make-buffers-in-spec gl glc/static-draw))
        tex-ready (volatile! 0)
        tex1      (buf/load-texture
                   gl {:callback (fn [tex img] (vswap! tex-ready inc))
                       :src      "../assets/cubev.png"
                       :flip     false})
        tex2      (buf/load-texture
                   gl {:callback (fn [tex img] (vswap! tex-ready inc))
                       :src      "../assets/lancellotti.jpg"
                       :flip     false})]
    (anim/animate
     (fn [t frame]
       (when (= @tex-ready 2)
         ;; bind both textures
         ;; shader will x-fade between them based on :fade uniform value
         (gl/bind tex1 0)
         (gl/bind tex2 1)
         (doto gl
           (gl/set-viewport view-rect)
           (gl/clear-color-and-depth-buffer col/WHITE 1)
           (gl/draw-with-shader
            (update model :uniforms merge
                    {:model (-> M44 (g/rotate-x PI) (g/rotate-y (* t 0.5)))
                     :fade  (+ 0.5 (* 0.5 (Math/sin (* t 2))))}))))
       true))))

Example 6: FBO & Render to texture

Live version: http://demo.thi.ng/geom/webgl/fbo-rtt/ & http://demo.thi.ng/geom/webgl/fbo-rtt2/

(ns thi.ng.geom.examples.gl.ex06
  <<require-macros>>
  (:require
   <<default-requires>>
   [thi.ng.geom.sphere :as s]
   [thi.ng.geom.gl.shaders.lambert :as lambert]
   [thi.ng.geom.gl.shaders.image :as image]
   [thi.ng.geom.gl.fx :as fx]
   [thi.ng.glsl.vertex :as vertex]))

(def shader-spec
  {:vs (->> "void main() {
               vUV = uv;
               vec3 p = rotateZ(position, position.z * sin(time) + time);
               gl_Position = proj * view * model * vec4(p, 1.0);
             }"
            (glsl/minified)
            (glsl/glsl-spec-plain [vertex/rotate-z])
            (glsl/assemble))
   :fs (->> "void main(){
               gl_FragColor = texture2D(tex, vUV);
             }"
            (glsl/minified))
   :uniforms {:model    [:mat4 M44]
              :view     :mat4
              :proj     :mat4
              :tex      [:sampler2D 0]
              :time     :float}
   :attribs  {:position :vec3
              :uv       :vec2}
   :varying  {:vUV      :vec2}
   :state    {:depth-test true}})

(defn ^:export demo
  []
  (enable-console-print!)
  (let [gl             (gl/gl-context "main")
        view-rect      (gl/get-viewport-rect gl)
        main-shader    (sh/make-shader-from-spec gl shader-spec)
        lambert-shader (sh/make-shader-from-spec gl lambert/shader-spec-attrib)
        fbo-size       512
        fbo-tex        (buf/make-texture
                        gl {:width  fbo-size
                            :height fbo-size
                            :filter glc/linear
                            :wrap   glc/clamp-to-edge})
        fbo            (buf/make-fbo-with-attachments
                        gl {:tex    fbo-tex
                            :width  fbo-size
                            :height fbo-size
                            :depth? true})
        quad           (-> (fx/init-fx-quad gl)
                           (assoc :shader (sh/make-shader-from-spec gl fx/shader-spec))
                           (assoc-in [:shader :state :tex] fbo-tex))
        model1         (-> (s/sphere 1)
                           (g/as-mesh
                            {:mesh    (glm/gl-mesh 64 #{:col :fnorm})
                             :attribs {:col (fn [_ _ v _] (col/rgba (m/madd (m/normalize v) 0.5 0.5)))}
                             :res     6})
                           (gl/as-gl-buffer-spec {})
                           (cam/apply (cam/perspective-camera {:eye (vec3 0 0 3) :aspect 1.0}))
                           (assoc :shader lambert-shader)
                           (gl/make-buffers-in-spec gl glc/static-draw))
        model2         (-> (s/sphere 2.5)
                           (g/as-mesh
                            {:mesh    (glm/gl-mesh 2048 #{:uv})
                             :attribs {:uv attr/uv-faces}
                             :res     32})
                           (gl/as-gl-buffer-spec {})
                           (cam/apply (cam/perspective-camera {:fov 90 :aspect view-rect}))
                           (assoc :shader main-shader)
                           (gl/make-buffers-in-spec gl glc/static-draw))]

    (anim/animate
     (fn [t frame]
       ;; render pass #1: Render to FBO
       (gl/bind fbo)
       (doto gl
         (gl/set-viewport 0 0 fbo-size fbo-size)
         (gl/clear-color-and-depth-buffer col/BLACK 1)
         (gl/draw-with-shader
          (assoc-in model1 [:uniforms :model]
                    (-> M44 (g/rotate-x t) (g/rotate-y (* t 2))))))
       (gl/unbind fbo)

       ;; render pass #2: Render with FBO as texture
       (gl/bind fbo-tex)
       (doto gl
         (gl/set-viewport view-rect)
         (gl/clear-color-and-depth-buffer 0.8 0.8 0.8 1 1)
         (gl/draw-with-shader
          (update model2 :uniforms merge
                  {:model (-> M44 (g/rotate-x (* t -0.25)) (g/rotate-y (* t -0.45)))
                   :time  t})))
       (gl/unbind fbo-tex)

       ;; render pass #3: Draw FBO texture as 2D image
       (doto gl
         (gl/set-viewport 0 0 128 128)
         (gl/draw-with-shader quad))

       true))))

Example 7: STL mesh loading

This example shows how to load and parse a binary STL mesh via XHR and then interactively control the camera via the arcball controller.

Live demo: http://demo.thi.ng/geom/webgl/stl-mesh/

(ns thi.ng.geom.examples.gl.ex07
  <<require-macros>>
  (:require
   <<default-requires>>
   [thi.ng.geom.mesh.io :as mio]
   [thi.ng.geom.quaternion :as q]
   [thi.ng.geom.gl.arcball :as arc]
   [thi.ng.geom.gl.shaders.phong :as phong]))

(enable-console-print!)

(def state (atom {}))

(defn load-binary
  [uri onload onerror]
  (let [xhr (js/XMLHttpRequest.)]
    (set! (.-responseType xhr) "arraybuffer")
    (set! (.-onload xhr)
          (fn [e]
            (if-let [buf (.-response xhr)]
              (onload buf)
              (when onerror (onerror xhr e)))))
    (doto xhr
      (.open "GET" uri true)
      (.send))))

(defn init-model
  [gl vrect buf]
  (let [model (-> (mio/read-stl (mio/wrapped-input-stream buf) #(glm/gl-mesh % #{:fnorm}))
                  (gl/as-gl-buffer-spec {})
                  (assoc :shader (sh/make-shader-from-spec gl phong/shader-spec))
                  (update :uniforms merge
                          {:proj          (mat/perspective 60 vrect 0.1 10)
                           :view          (mat/look-at (v/vec3 0 0 1) (v/vec3) v/V3Y)
                           :lightPos      (vec3 0.1 0 1)
                           :ambientCol    0x000011
                           :diffuseCol    0x0033ff
                           :specularCol   0xffffff
                           :shininess     100
                           :wrap          0
                           :useBlinnPhong true})
                  (gl/make-buffers-in-spec gl glc/static-draw)
                  (time))]
    (swap! state assoc :model model)))

(defn init-arcball
  [el vrect]
  (swap! state assoc :cam
         (-> (arc/arcball {:init (m/normalize (q/quat 0.0 0.707 0.707 0))})
             (arc/resize (g/width vrect) (g/height vrect))))
  (doto el
    (.addEventListener
     "mousedown"
     (fn [e]
       (doto state
         (swap! assoc :mouse-down true)
         (swap! update :cam arc/down (.-clientX e) (.-clientY e)))))
    (.addEventListener
     "mouseup"
     (fn [e] (swap! state assoc :mouse-down false)))
    (.addEventListener
     "mousemove"
     (fn [e]
       (when (:mouse-down @state)
         (swap! state update :cam arc/drag (.-clientX e) (.-clientY e)))))))

(defn ^:export demo
  []
  (let [gl    (gl/gl-context "main")
        vrect (gl/get-viewport-rect gl)]
    (load-binary
     "dev-resources/suzanne.stl"
     (fn [buf] (init-model gl vrect buf))
     (fn [req e] (prn "error loading model")))
    (init-arcball (.getElementById js/document "main") vrect)
    (anim/animate
     (fn [t frame]
       (when-let [model (:model @state)]
         (doto gl
           (gl/set-viewport vrect)
           (gl/clear-color-and-depth-buffer col/WHITE 1)
           (gl/draw-with-shader
            (assoc-in model [:uniforms :model] (arc/get-view (:cam @state))))))
       true))))

Example: Morphogen & shadow mapping

Note: This example needs updating and doesn’t work at the moment…

;;:tangle ../../babel/test/thi/ng/geom/gl/example07.cljs :noweb yes :mkdirp yes :padline no
 (ns thi.ng.geom.gl.example07
   <<require-macros>>
   (:require
    <<default-requires>>
    [thi.ng.geom.gl.shaders.shadow :as shadow]
    [thi.ng.geom.gl.shaders.image :as image]
    [thi.ng.geom.aabb :as a]
    [thi.ng.geom.basicmesh :refer [basic-mesh]]
    [thi.ng.morphogen.core :as mg]))

 <<stats>>

 (defn mg-hex-sphere
   []
   (let [hex           (mg/apply-recursively (mg/reflect :dir :e) 5 [1] 1)
         reflected-hex (mg/reflect :dir :n :out [{} hex])
         inject        #(-> hex
                            (assoc-in (mg/child-path [1 1 0]) %)
                            (assoc-in (mg/child-path [1 1 1 1 0]) %))
         seed-clone    (mg/reflect
                        :dir :s :out [{} (inject reflected-hex)])
         tree          (mg/reflect
                        :dir :s :out [(inject seed-clone) (inject reflected-hex)])]
     (-> (mg/seed (mg/sphere-lattice-seg 6 0.25 0.0955 0.2))
         (mg/walk tree)
         (mg/union-mesh (basic-mesh))
         (g/transform (-> M44 (g/rotate-x (- HALF_PI)) (g/scale 0.5))))))

 (defn ^:export demo
   []
   (enable-console-print!)
   (let [gl           (gl/gl-context "main")
         light-shader (sh/make-shader-from-spec gl shadow/light-pass-spec)
         cam-shader   (sh/make-shader-from-spec gl shadow/cam-pass-spec)
         mesh         (mg-hex-sphere)
         room         (-> (a/aabb 5)
                          (g/center (vec3 0 2.4 0))
                          (g/as-mesh)
                          (g/flip))
         mesh         (g/into mesh room)
         model        (-> mesh
                          (gl/as-gl-buffer-spec {})
                          (gl/make-buffers-in-spec gl glc/static-draw))
         ov-quad      (image/make-shader-spec gl)
         view-rect    (gl/get-viewport-rect gl)
         cam-proj     (gl/perspective 45 view-rect 0.1 100.0)
         light-proj   (gl/perspective 45 1 0.01 100)
         light-fbo    (shadow/init-light-fbo gl 512)
         stats        (init-stats)]

     (anim/animate
      (fn [t frame]
        (let [y          (m/map-interval (Math/sin (* t 0.5)) -1 1 0 3)
              cam-view   (-> (vec3 0 y 2)
                             (g/rotate-y (* t 0.4))
                             (mat/look-at (vec3 0 0 0) (vec3 0 1 0)))
              model-tx   (-> M44 (g/rotate-x (* 0 0.15)))
              cone-angle (m/map-interval (Math/cos (* t 0.3)) -1 1 30 90)
              lr         (* (m/map-interval (Math/cos (* t 0.7)) -1 1 -0.85 0.85) PI)
              light-view (mat/look-at (g/rotate-y (vec3 0 2.4 2.4) lr) (vec3 0 0 0) (vec3 0 1 0))
              light-rot  (mat/matrix44->matrix33-rot light-view)]

          (shadow/draw-light-pass
           gl light-fbo
           #(buf/draw
             gl (assoc model
                  :shader  light-shader
                  :attribs (select-keys (:attribs model) [:position])
                  :uniforms {:model model-tx
                             :view  light-view
                             :proj  light-proj})))
          (gl/set-viewport gl view-rect)
          (gl/clear-color-buffer gl 0.08 0.1 0.12 1.0)
          (shadow/draw-cam-pass
           gl (:tex light-fbo)
           (fn [uniforms]
             (buf/draw
              gl (assoc model
                   :shader   cam-shader
                   :uniforms (assoc uniforms
                               :model     model-tx
                               :view      cam-view
                               :proj      cam-proj
                               :lightView light-view
                               :lightProj light-proj
                               :lightRot  light-rot
                               :coneAngle cone-angle)))))

          (gl/disable gl glc/depth-test)
          (gl/disable gl glc/cull-face)
          (image/draw
           gl (assoc ov-quad
                :viewport view-rect
                :pos [(m/map-interval (Math/sin t) -1 1 0 512) 352]
                :width 128
                :height 128))

          (update-stats stats)
          ;; keep animating...
          true)))))