Skip to content

Commit d0aa7c4

Browse files
committed
Add clouds with Mie scattering and shadows
1 parent 1b0a92c commit d0aa7c4

10 files changed

Lines changed: 536 additions & 96 deletions

File tree

site/volumetric_clouds/clouds.jpg

9.48 KB
Loading

site/volumetric_clouds/main.qmd

Lines changed: 397 additions & 52 deletions
Large diffs are not rendered by default.
148 KB
Loading
140 KB
Loading
13.4 KB
Loading
-14.7 KB
Loading
131 KB
Loading
140 KB
Loading

src/volumetric_clouds/clouds.jpg

9.48 KB
Loading

src/volumetric_clouds/main.clj

Lines changed: 139 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
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 %>
840848
float <%= 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
858885
out vec4 fragColor;
859-
vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval, float step);
886+
vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval);
860887
void 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
891917
uniform vec2 resolution;
918+
uniform vec3 light;
892919
uniform mat3 rotation;
893920
uniform float focal_length;
894921
uniform float distance;
895922
out vec4 fragColor;
896923
vec2 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);
898925
void 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

Comments
 (0)