1010 :tags [:visualization ]}}}
1111
1212(ns volumetric-clouds.main
13- (:require [clojure.math :refer (sqrt )]
13+ (:require [clojure.math :refer (sqrt tan to-radians )]
1414 [midje.sweet :refer (fact facts tabular => roughly)]
1515 [fastmath.vector :refer (vec2 vec3 add mult sub div mag dot)]
16+ [fastmath.matrix :refer (mat->float-array mulm rotation-matrix-3d-x rotation-matrix-3d-y)]
1617 [tech.v3.datatype :as dtype]
1718 [tech.v3.tensor :as tensor]
1819 [tech.v3.datatype.functional :as dfn]
@@ -639,29 +640,27 @@ void main()
639640 (GL20/glEnableVertexAttribArray point-attribute)))
640641
641642
642- (defn render-pixels
643- [vertex-sources fragment-sources width height ]
643+ (defn render-pixel
644+ [vertex-sources fragment-sources]
644645 (let [vertices (float-array [1.0 1.0 0.0 , -1.0 1.0 0.0 , -1.0 -1.0 0.0 , 1.0 -1.0 0.0 ])
645646 indices (int-array [0 1 2 3 ])
646- vertex-shader (map #(make-shader % GL20/GL_VERTEX_SHADER) vertex-sources)
647+ vertex-shaders (map #(make-shader % GL20/GL_VERTEX_SHADER) vertex-sources)
647648 fragment-shaders (map #(make-shader % GL20/GL_FRAGMENT_SHADER) fragment-sources)
648- program (apply make-program (concat vertex-shader fragment-shaders))
649+ program (apply make-program (concat vertex-shaders fragment-shaders))
649650 vao (setup-vao vertices indices)
650- texture (make-texture width height )]
651+ texture (make-texture 1 1 )]
651652 (setup-point-attribute program)
652- (framebuffer-render texture width height
653+ (framebuffer-render texture 1 1
653654 (GL20/glUseProgram program)
654- (GL11/glClearColor 1.0 0.5 0.25 1.0 )
655- (GL11/glClear GL11/GL_COLOR_BUFFER_BIT)
656655 (GL11/glDrawElements GL11/GL_QUADS (count indices) GL11/GL_UNSIGNED_INT 0 ))
657- (let [result (read-texture texture width height )]
656+ (let [result (read-texture texture 1 1 )]
658657 (GL11/glDeleteTextures texture)
659658 (teardown-vao vao)
660659 (GL20/glDeleteProgram program)
661660 result)))
662661
663662
664- (render-pixels [vertex-test] [fragment-test] 1 1 )
663+ (render-pixel [vertex-test] [fragment-test])
665664
666665
667666(def noise-mock
@@ -685,7 +684,7 @@ void main()
685684
686685
687686(tabular " Test noise mock"
688- (fact (nth (render-pixels [vertex-test] [noise-mock (noise-probe ?x ?y ?z)] 1 1 ) 0 ) => ?result)
687+ (fact (nth (render-pixel [vertex-test] [noise-mock (noise-probe ?x ?y ?z)]) 0 ) => ?result)
689688 ?x ?y ?z ?result
690689 0 0 0 0.0
691690 1 0 0 1.0
@@ -725,7 +724,7 @@ void main()
725724
726725
727726(tabular " Test octaves of noise"
728- (fact (nth (render-pixels [vertex-test] [noise-mock (noise-octaves ?octaves) (octaves-probe ?x ?y ?z)] 1 1 ) 0 )
727+ (fact (first (render-pixel [vertex-test] [noise-mock (noise-octaves ?octaves) (octaves-probe ?x ?y ?z)]) )
729728 => ?result)
730729 ?x ?y ?z ?octaves ?result
731730 0 0 0 [1.0 ] 0.0
@@ -770,7 +769,7 @@ void main()
770769
771770
772771(tabular " Test intersection of ray with box"
773- (fact ((juxt first second) (render-pixels [vertex-test] [ray-box (ray-box-probe ?ox ?oy ?oz ?dx ?dy ?dz)] 1 1 ))
772+ (fact ((juxt first second) (render-pixel [vertex-test] [ray-box (ray-box-probe ?ox ?oy ?oz ?dx ?dy ?dz)]))
774773 => ?result)
775774 ?ox ?oy ?oz ?dx ?dy ?dz ?result
776775 -2 0 0 1 0 0 [1.0 3.0 ]
@@ -795,6 +794,106 @@ float cloud(vec3 idx)
795794}" ))
796795
797796
797+ (def cloud-transfer
798+ " #version 130
799+ float cloud(vec3 idx);
800+ vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval, float step)
801+ {
802+ vec4 result = vec4(0, 0, 0, 0);
803+ for (float t = interval.x + 0.5 * step; t < interval.y; t += step) {
804+ float density = cloud(origin + direction * t);
805+ float transmittance = exp(-density * step);
806+ vec3 color = vec3(1.0, 1.0, 1.0);
807+ result.rgb = result.rgb + color * (1.0 - result.a) * (1.0 - transmittance);
808+ result.a = 1.0 - (1.0 - result.a) * transmittance;
809+ };
810+ return result;
811+ }" )
812+
813+
814+ (def cloud-transfer-probe
815+ (template/fn [a b step]
816+ " #version 130
817+ out vec4 fragColor;
818+ vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval, float step);
819+ void main()
820+ {
821+ vec3 origin = vec3(0, 0, 0);
822+ vec3 direction = vec3(1, 0, 0);
823+ vec2 interval = vec2(<%= a %>, <%= b %>);
824+ float step = <%= step %>;
825+ fragColor = cloud_transfer(origin, direction, interval, step);
826+ }" ))
827+
828+
829+ (defn roughly-vector
830+ [expected error]
831+ (fn [actual]
832+ (and (== (count expected) (count actual))
833+ (<= (apply + (mapv (fn [a b] (* (- b a) (- b a))) actual expected)) (* error error)))))
834+
835+
836+ (tabular " Test cloud transfer"
837+ (fact (seq (render-pixel [vertex-test] [(cloud-mock ?density) cloud-transfer (cloud-transfer-probe ?a ?b ?step)]))
838+ => (roughly-vector ?result 1e-3 ))
839+ ?a ?b ?step ?density ?result
840+ 0 0 1 0.0 [0.0 0.0 0.0 0.0 ]
841+ 0 1 1 1.0 [0.632 0.632 0.632 0.632 ]
842+ 0 1 0.5 1.0 [0.632 0.632 0.632 0.632 ]
843+ 0 1 0.5 0.5 [0.393 0.393 0.393 0.393 ])
844+
845+
846+ (def fragment-cloud
847+ " #version 130
848+ uniform vec2 resolution;
849+ uniform mat3 rotation;
850+ uniform float focal_length;
851+ uniform float distance;
852+ out vec4 fragColor;
853+ vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
854+ vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval, float step);
855+ void main()
856+ {
857+ vec3 direction = normalize(rotation * vec3(gl_FragCoord.xy - 0.5 * resolution, focal_length));
858+ vec3 origin = rotation * vec3(0, 0, -distance);
859+ vec2 interval = ray_box(vec3(-1, -1, -1), vec3(1, 1, 1), origin, direction);
860+ vec4 transfer = cloud_transfer(origin, direction, interval, 0.01);
861+ vec3 background = vec3(0.5, 0.5, 1.0);
862+ fragColor = vec4(background * (1.0 - transfer.a) + transfer.rgb, 1.0);
863+ }" )
864+
865+
866+ (defn render-clouds
867+ [width height]
868+ (let [vertices (float-array [1.0 1.0 0.0 , -1.0 1.0 0.0 , -1.0 -1.0 0.0 , 1.0 -1.0 0.0 ])
869+ indices (int-array [0 1 2 3 ])
870+ rotation (mulm (rotation-matrix-3d-x (to-radians -25.0 )) (rotation-matrix-3d-y (to-radians 30.0 )))
871+ vertex-shader (make-shader vertex-test GL20/GL_VERTEX_SHADER)
872+ fragment-sources [ray-box cloud-transfer (cloud-mock 0.3 ) fragment-cloud]
873+ fragment-shaders (map #(make-shader % GL20/GL_FRAGMENT_SHADER) fragment-sources)
874+ program (apply make-program vertex-shader fragment-shaders)
875+ vao (setup-vao vertices indices)
876+ texture (make-texture width height)]
877+ (setup-point-attribute program)
878+ (framebuffer-render texture width height
879+ (GL20/glUseProgram program)
880+ (GL20/glUniform2f (GL20/glGetUniformLocation program " resolution" ) width height)
881+ (GL20/glUniformMatrix3fv (GL20/glGetUniformLocation program " rotation" ) true
882+ (make-float-buffer (mat->float-array rotation)))
883+ (GL20/glUniform1f (GL20/glGetUniformLocation program " focal_length" ) (/ (* 0.5 width) (tan (to-radians 30.0 ))))
884+ (GL20/glUniform1f (GL20/glGetUniformLocation program " distance" ) 4.0 )
885+ (GL11/glDrawElements GL11/GL_QUADS (count indices) GL11/GL_UNSIGNED_INT 0 ))
886+ (let [result (read-texture texture width height)]
887+ (GL11/glDeleteTextures texture)
888+ (teardown-vao vao)
889+ (GL20/glDeleteProgram program)
890+ result)))
891+
892+
893+ (def image (dfn/* 255 (tensor/select (tensor/reshape (tensor/->tensor (render-clouds 640 480 )) [480 640 4 ]) :all :all [2 1 0 ])))
894+
895+ (bufimg/tensor->image image)
896+
798897(GLFW/glfwDestroyWindow window)
799898
800899(GLFW/glfwTerminate )
0 commit comments