This file is archived and only kept for reference - DO NOT edit
- WebGL examples & test scenes
- Usage
- Templates for namespace setup
- Example 1: 2D polygons
- Example 2: Colored mesh
- Example 3: Extruded 2D polygons with Blinn-Phong shading
- Example 4: Unproject & scene raycasting
- Example 5: Texture mapping
- Example 5b: Multiple textures
- Example 6: FBO & Render to texture
- Example 7: STL mesh loading
- TODO Example: Morphogen & shadow mapping
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()
(: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))
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 valueshader2
is requesting to specify the color as a vertex attribute and we generate the required attribute buffer in the call toas-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))))
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))))
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)))))
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))
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))))
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))))
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))))
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))))
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)))))