1111 :tags [:visualization ]}}}
1212
1313(ns volumetric-clouds.main
14- (:require [clojure.math :refer (sqrt tan to-radians)]
14+ (:require [clojure.math :refer (PI sqrt tan to-radians pow )]
1515 [midje.sweet :refer (fact facts tabular => roughly)]
16- [fastmath.vector :refer (vec2 vec3 add mult sub div mag dot)]
17- [fastmath.matrix :refer (mat->float-array mulm rotation-matrix-3d-x rotation-matrix-3d-y)]
16+ [fastmath.vector :refer (vec2 vec3 add mult sub div mag dot normalize )]
17+ [fastmath.matrix :refer (mat->float-array mulm mulv inverse rotation-matrix-3d-x rotation-matrix-3d-y)]
1818 [tech.v3.datatype :as dtype]
1919 [tech.v3.tensor :as tensor]
2020 [tech.v3.datatype.functional :as dfn]
@@ -678,17 +678,24 @@ void main()
678678 (setup-vao vertices indices)))
679679
680680
681+ (defmacro render-array
682+ [width height & body]
683+ `(let [texture# (volumetric-clouds.main/make-texture-2d ~width ~height)]
684+ (volumetric-clouds.main/framebuffer-render texture# ~width ~height ~@body)
685+ (let [result# (volumetric-clouds.main/read-texture-2d texture# ~width ~height)]
686+ (GL11/glDeleteTextures texture#)
687+ result#)))
688+
689+
681690(defn render-pixel
682691 [vertex-sources fragment-sources]
683- (let [program (make-program-with-shaders vertex-sources fragment-sources)
684- vao (setup-quad-vao )
685- texture (make-texture-2d 1 1 )]
692+ (let [program (make-program-with-shaders vertex-sources fragment-sources)
693+ vao (setup-quad-vao )]
686694 (setup-point-attribute program)
687- (framebuffer-render texture 1 1
695+ (let [result
696+ (render-array 1 1
688697 (GL20/glUseProgram program)
689- (GL11/glDrawElements GL11/GL_QUADS 4 GL11/GL_UNSIGNED_INT 0 ))
690- (let [result (read-texture-2d texture 1 1 )]
691- (GL11/glDeleteTextures texture)
698+ (GL11/glDrawElements GL11/GL_QUADS 4 GL11/GL_UNSIGNED_INT 0 ))]
692699 (teardown-vao vao)
693700 (GL20/glDeleteProgram program)
694701 result)))
@@ -835,35 +842,54 @@ float fog(vec3 idx)
835842
836843
837844(def cloud-transfer
838- (template/fn [noise]
845+ (template/fn [noise step ]
839846" #version 130
847+ #define STEP <%= step %>
840848float <%= noise %>(vec3 idx);
841- vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval, float step)
849+ float in_scatter(vec3 point, vec3 direction);
850+ float shadow(vec3 point);
851+ vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval)
842852{
843853 vec4 result = vec4(0, 0, 0, 0);
844- for (float t = interval.x + 0.5 * step; t < interval.y; t += step) {
845- float density = <%= noise %>(origin + direction * t);
846- float transmittance = exp(-density * step);
847- vec3 color = vec3(1.0, 1.0, 1.0);
848- result.rgb = result.rgb + color * (1.0 - result.a) * (1.0 - transmittance);
854+ for (float t = interval.x + 0.5 * STEP; t < interval.y; t += STEP) {
855+ vec3 point = origin + direction * t;
856+ float density = <%= noise %>(point);
857+ float transmittance = exp(-density * STEP);
858+ vec3 color = vec3(in_scatter(point, direction) * shadow(point));
859+ result.rgb += color * (1.0 - result.a) * (1.0 - transmittance);
849860 result.a = 1.0 - (1.0 - result.a) * transmittance;
850861 };
851862 return result;
852863}" ))
853864
854865
866+ (def constant-scatter
867+ " #version 130
868+ float in_scatter(vec3 point, vec3 direction)
869+ {
870+ return 1.0;
871+ }" )
872+
873+
874+ (def no-shadow
875+ " #version 130
876+ float shadow(vec3 point)
877+ {
878+ return 1.0;
879+ }" )
880+
881+
855882(def cloud-transfer-probe
856- (template/fn [a b step ]
883+ (template/fn [a b]
857884" #version 130
858885out vec4 fragColor;
859- vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval, float step );
886+ vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval);
860887void main()
861888{
862889 vec3 origin = vec3(0, 0, 0);
863890 vec3 direction = vec3(1, 0, 0);
864891 vec2 interval = vec2(<%= a %>, <%= b %>);
865- float step = <%= step %>;
866- fragColor = cloud_transfer(origin, direction, interval, step);
892+ fragColor = cloud_transfer(origin, direction, interval);
867893}" ))
868894
869895
@@ -875,7 +901,7 @@ void main()
875901
876902
877903(tabular " Test cloud transfer"
878- (fact (seq (render-pixel [vertex-test] [(fog ?density) (cloud-transfer " fog" ) (cloud-transfer-probe ?a ?b ?step )]))
904+ (fact (seq (render-pixel [vertex-test] [(fog ?density) constant-scatter no-shadow (cloud-transfer " fog" ?step ) (cloud-transfer-probe ?a ?b)]))
879905 => (roughly-vector ?result 1e-3 ))
880906 ?a ?b ?step ?density ?result
881907 0 0 1 0.0 [0.0 0.0 0.0 0.0 ]
@@ -889,54 +915,55 @@ void main()
889915(def fragment-cloud
890916" #version 130
891917uniform vec2 resolution;
918+ uniform vec3 light;
892919uniform mat3 rotation;
893920uniform float focal_length;
894921uniform float distance;
895922out vec4 fragColor;
896923vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
897- vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval, float step );
924+ vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval);
898925void main()
899926{
900927 vec3 direction = normalize(rotation * vec3(gl_FragCoord.xy - 0.5 * resolution, focal_length));
901928 vec3 origin = rotation * vec3(0, 0, -distance);
902929 vec2 interval = ray_box(vec3(-0.5, -0.5, -0.5), vec3(0.5, 0.5, 0.5), origin, direction);
903- vec4 transfer = cloud_transfer(origin, direction, interval, 0.01 );
904- vec3 background = vec3(0.125, 0.125, 0.25);
930+ vec4 transfer = cloud_transfer(origin, direction, interval);
931+ vec3 background = mix( vec3(0.125, 0.125, 0.25), vec3(1, 1, 1), pow(dot(direction, light), 1000.0) );
905932 fragColor = vec4(background * (1.0 - transfer.a) + transfer.rgb, 1.0);
906933}" )
907934
908935
909936(defn setup-fog-uniforms
910937 [program width height]
911938 (let [rotation (mulm (rotation-matrix-3d-y (to-radians 30.0 )) (rotation-matrix-3d-x (to-radians -20.0 )))
912- focal-length (/ (* 0.5 width) (tan (to-radians 25.0 )))]
939+ focal-length (/ (* 0.5 width) (tan (to-radians 30.0 )))
940+ light (normalize (vec3 4 1 10 ))]
913941 (GL20/glUseProgram program)
914942 (GL20/glUniform2f (GL20/glGetUniformLocation program " resolution" ) width height)
943+ (GL20/glUniform3f (GL20/glGetUniformLocation program " light" ) (light 0 ) (light 1 ) (light 2 ))
915944 (GL20/glUniformMatrix3fv (GL20/glGetUniformLocation program " rotation" ) true
916945 (make-float-buffer (mat->float-array rotation)))
917946 (GL20/glUniform1f (GL20/glGetUniformLocation program " focal_length" ) focal-length)
918- (GL20/glUniform1f (GL20/glGetUniformLocation program " distance" ) 2.5 )))
947+ (GL20/glUniform1f (GL20/glGetUniformLocation program " distance" ) 2.0 )))
919948
920949
921950(defn render-fog
922951 [width height]
923- (let [fragment-sources [ray-box (cloud-transfer " fog" ) (fog 1.0 ) fragment-cloud]
952+ (let [fragment-sources [ray-box constant-scatter no-shadow (cloud-transfer " fog" 0.01 ) (fog 1.0 ) fragment-cloud]
924953 program (make-program-with-shaders [vertex-test] fragment-sources)
925- vao (setup-quad-vao )
926- texture (make-texture-2d width height)]
954+ vao (setup-quad-vao )]
927955 (setup-point-attribute program)
928- (framebuffer-render texture width height
956+ (let [result
957+ (render-array width height
929958 (setup-fog-uniforms program width height)
930- (GL11/glDrawElements GL11/GL_QUADS 4 GL11/GL_UNSIGNED_INT 0 ))
931- (let [result (read-texture-2d texture width height)]
932- (GL11/glDeleteTextures texture)
959+ (GL11/glDrawElements GL11/GL_QUADS 4 GL11/GL_UNSIGNED_INT 0 ))]
933960 (teardown-vao vao)
934961 (GL20/glDeleteProgram program)
935962 result)))
936963
937964
938965(defn rgba-array->bufimg [data width height]
939- (-> data tensor/->tensor (tensor/reshape [height width 4 ]) (tensor/select :all :all [2 1 0 ]) (dfn/* 255 )))
966+ (-> data tensor/->tensor (tensor/reshape [height width 4 ]) (tensor/select :all :all [2 1 0 ]) (dfn/* 255 ) ( clamp 0 255 ) ))
940967
941968
942969(bufimg/tensor->image (rgba-array->bufimg (render-fog 640 480 ) 640 480 ))
@@ -985,20 +1012,18 @@ float noise(vec3 idx)
9851012 [width height & cloud-shaders]
9861013 (let [fragment-sources (concat cloud-shaders [ray-box fragment-cloud])
9871014 program (make-program-with-shaders [vertex-test] fragment-sources)
988- vao (setup-quad-vao )
989- texture (make-texture-2d width height)]
1015+ vao (setup-quad-vao )]
9901016 (setup-point-attribute program)
991- (framebuffer-render texture width height
1017+ (let [result
1018+ (render-array width height
9921019 (setup-noise-uniforms program width height)
993- (GL11/glDrawElements GL11/GL_QUADS 4 GL11/GL_UNSIGNED_INT 0 ))
994- (let [result (read-texture-2d texture width height)]
995- (GL11/glDeleteTextures texture)
1020+ (GL11/glDrawElements GL11/GL_QUADS 4 GL11/GL_UNSIGNED_INT 0 ))]
9961021 (teardown-vao vao)
9971022 (GL20/glDeleteProgram program)
9981023 result)))
9991024
10001025
1001- (bufimg/tensor->image (rgba-array->bufimg (render-noise 640 480 (cloud-transfer " noise" ) noise-shader) 640 480 ))
1026+ (bufimg/tensor->image (rgba-array->bufimg (render-noise 640 480 constant-scatter no-shadow (cloud-transfer " noise" 0.01 ) noise-shader) 640 480 ))
10021027
10031028
10041029; ; # Remap and clamp 3D noise
@@ -1049,12 +1074,82 @@ float remap_noise(vec3 idx)
10491074}" ))
10501075
10511076
1052- (bufimg/tensor->image (rgba-array->bufimg (render-noise 640 480 (cloud-transfer " remap_noise" ) remap-clamp (remap-noise " noise" 0.45 0.9 1.5 ) noise-shader) 640 480 ))
1077+ (def cloud-strength 5.0 )
1078+
1079+
1080+ (bufimg/tensor->image (rgba-array->bufimg (render-noise 640 480 constant-scatter no-shadow (cloud-transfer " remap_noise" 0.01 ) remap-clamp (remap-noise " noise" 0.45 0.9 cloud-strength) noise-shader) 640 480 ))
10531081
10541082
10551083; ; # Octaves of 3D noise
10561084
1057- (bufimg/tensor->image (rgba-array->bufimg (render-noise 640 480 (cloud-transfer " remap_noise" ) remap-clamp (remap-noise " octaves" 0.45 0.9 1.5 ) (noise-octaves (octaves 4 0.5 )) noise-shader) 640 480 ))
1085+ (bufimg/tensor->image (rgba-array->bufimg (render-noise 640 480 constant-scatter no-shadow (cloud-transfer " remap_noise" 0.01 ) remap-clamp (remap-noise " octaves" 0.45 0.9 cloud-strength) (noise-octaves (octaves 4 0.5 )) noise-shader) 640 480 ))
1086+
1087+
1088+ (def mie-scatter
1089+ (template/fn [g]
1090+ " #version 450 core
1091+ #define M_PI 3.1415926535897932384626433832795
1092+ #define ANISOTROPIC 0.25
1093+ #define G <%= g %>
1094+ uniform vec3 light;
1095+ float mie(float mu)
1096+ {
1097+ return 3 * (1 - G * G) * (1 + mu * mu) / (8 * M_PI * (2 + G * G) * pow(1 + G * G - 2 * G * mu, 1.5));
1098+ }
1099+ float in_scatter(vec3 point, vec3 direction)
1100+ {
1101+ return mix(1.0, mie(dot(light, direction)), ANISOTROPIC);
1102+ }" ))
1103+
1104+
1105+ ; ; # Mie scattering
1106+ (def mie-probe
1107+ (template/fn [mu]
1108+ " #version 450 core
1109+ out vec4 fragColor;
1110+ float mie(float mu);
1111+ void main()
1112+ {
1113+ float result = mie(<%= mu %>);
1114+ fragColor = vec4(result, 0, 0, 1);
1115+ }" ))
1116+
1117+
1118+ (tabular " Shader function for scattering phase function"
1119+ (fact (first (render-pixel [vertex-test] [(mie-scatter ?g) (mie-probe ?mu)])) => (roughly ?result 1e-6 ))
1120+ ?g ?mu ?result
1121+ 0 0 (/ 3 (* 16 PI))
1122+ 0 1 (/ 6 (* 16 PI))
1123+ 0 -1 (/ 6 (* 16 PI))
1124+ 0.5 0 (/ (* 3 0.75 ) (* 8 PI 2.25 (pow 1.25 1.5 )))
1125+ 0.5 1 (/ (* 6 0.75 ) (* 8 PI 2.25 (pow 0.25 1.5 ))))
1126+
1127+
1128+ (bufimg/tensor->image (rgba-array->bufimg (render-noise 640 480 (mie-scatter 0.76 ) no-shadow (cloud-transfer " remap_noise" 0.01 ) remap-clamp (remap-noise " octaves" 0.45 0.9 cloud-strength) (noise-octaves (octaves 4 0.5 )) noise-shader) 640 480 ))
1129+
1130+
1131+ ; ; # Self-shading of clouds
1132+ (def shadow
1133+ (template/fn [noise step]
1134+ " #version 130
1135+ #define STEP <%= step %>
1136+ uniform vec3 light;
1137+ float <%= noise %>(vec3 idx);
1138+ vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
1139+ float shadow(vec3 point)
1140+ {
1141+ vec2 interval = ray_box(vec3(-0.5, -0.5, -0.5), vec3(0.5, 0.5, 0.5), point, light);
1142+ float result = 1.0;
1143+ for (float t = interval.x + 0.5 * STEP; t < interval.y; t += STEP) {
1144+ float density = <%= noise %>(point + t * light);
1145+ float transmittance = exp(-density * STEP);
1146+ result *= transmittance;
1147+ };
1148+ return result;
1149+ }" ))
1150+
1151+
1152+ (bufimg/tensor->image (rgba-array->bufimg (render-noise 640 480 (mie-scatter 0.76 ) (shadow " remap_noise" 0.01 ) (cloud-transfer " remap_noise" 0.01 ) remap-clamp (remap-noise " octaves" 0.45 0.9 cloud-strength) (noise-octaves (octaves 4 0.5 )) noise-shader) 640 480 ))
10581153
10591154
10601155(GL11/glDeleteTextures noise-texture)
0 commit comments