From b9bd4c3a3df0762532170296a15a453c8a9f5d09 Mon Sep 17 00:00:00 2001 From: Digvijaysinh Gohil Date: Wed, 27 Mar 2024 11:23:17 +0530 Subject: [PATCH] Ray marching node finalized --- addons/ShaderLib/RayMarching/RayMarch.gd | 242 ++++++++++++++---- ...gdshaderinc => RayMarchCustom.gdshaderinc} | 11 +- .../RayMarching/RayMarchRotation.gdshaderinc | 7 + .../ShaderLib/RayMarching/SDBox.gdshaderinc | 21 ++ .../RayMarching/SDCapsule.gdshaderinc | 33 +++ .../RayMarching/SDCylinder.gdshaderinc | 37 +++ .../RayMarching/SDSphere.gdshaderinc | 23 ++ .../ShaderLib/RayMarching/SDTorus.gdshaderinc | 21 ++ documentation/Nodes/RayMarching/RayMarch.md | 8 +- 9 files changed, 354 insertions(+), 49 deletions(-) rename addons/ShaderLib/RayMarching/{RayMarch.gdshaderinc => RayMarchCustom.gdshaderinc} (54%) create mode 100644 addons/ShaderLib/RayMarching/RayMarchRotation.gdshaderinc create mode 100644 addons/ShaderLib/RayMarching/SDBox.gdshaderinc create mode 100644 addons/ShaderLib/RayMarching/SDCapsule.gdshaderinc create mode 100644 addons/ShaderLib/RayMarching/SDCylinder.gdshaderinc create mode 100644 addons/ShaderLib/RayMarching/SDSphere.gdshaderinc create mode 100644 addons/ShaderLib/RayMarching/SDTorus.gdshaderinc diff --git a/addons/ShaderLib/RayMarching/RayMarch.gd b/addons/ShaderLib/RayMarching/RayMarch.gd index 7fea588..acab6b9 100644 --- a/addons/ShaderLib/RayMarching/RayMarch.gd +++ b/addons/ShaderLib/RayMarching/RayMarch.gd @@ -8,52 +8,170 @@ func _get_category() -> String: return "RayMarching" func _get_description() -> String: - return "A simple ray marcher." + return "A simple ray marcher for primitive shapes." func _get_return_icon_type() -> PortType: return PORT_TYPE_SCALAR func _get_input_port_count() -> int: - return 6 + var sdf_index: int = get_option_index(0) + match sdf_index: + 2, 3, 4: + return 9 + _: + return 8 func _get_input_port_name(port: int) -> String: - match port: - 0: - return "signed distance" - 1: - return "ray origin" - 2: - return "ray direction" - 3: - return "max steps" - 4: - return "max distance" + var sdf_index: int = get_option_index(0) + match sdf_index: + 0, 1: + match port: + 0: + return "ray origin" + 1: + return "ray direction" + 2: + return "max steps" + 3: + return "max distance" + 4: + return "distance threshold" + 5: + return "cube pos" if sdf_index == 0 else "sphere pos" + 6: + return "cube eulers" if sdf_index == 0 else "sphere eulers" + _: + return "cube scale" if sdf_index == 0 else "sphere scale" + 2, 3: + match port: + 0: + return "ray origin" + 1: + return "ray direction" + 2: + return "max steps" + 3: + return "max distance" + 4: + return "distance threshold" + 5: + return "capsule pos" if sdf_index == 2 else "cylinder pos" + 6: + return "capsule eulers" if sdf_index == 2 else "cylinder eulers" + 7: + return "capsule height" if sdf_index == 2 else "cylinder height" + _: + return "capsule radius" if sdf_index == 2 else "cylinder radius" _: - return "distance threshold" + match port: + 0: + return "ray origin" + 1: + return "ray direction" + 2: + return "max steps" + 3: + return "max distance" + 4: + return "distance threshold" + 5: + return "torus pos" + 6: + return "torus eulers" + 7: + return "torus small radius" + _: + return "torus big radius" func _get_input_port_type(port: int) -> PortType: - match port: - 1, 2: - return PORT_TYPE_VECTOR_3D - 3: - return PORT_TYPE_SCALAR_INT + var sdf_index: int = get_option_index(0) + match sdf_index: + 0, 1: + match port: + 0, 1, 5, 6, 7: + return PORT_TYPE_VECTOR_3D + 2: + return PORT_TYPE_SCALAR_INT + _: + return PORT_TYPE_SCALAR + 2, 3: + match port: + 0, 1, 5, 6: + return PORT_TYPE_VECTOR_3D + 2: + return PORT_TYPE_SCALAR_INT + _: + return PORT_TYPE_SCALAR _: - return PORT_TYPE_SCALAR + match port: + 0, 1, 5, 6: + return PORT_TYPE_VECTOR_3D + 2: + return PORT_TYPE_SCALAR_INT + _: + return PORT_TYPE_SCALAR func _get_input_port_default_value(port: int) -> Variant: - match port: - 1: - return Vector3(0, 0, -1) - 2: - return Vector3(0 ,0 ,0) - 3: - return 15 - 4: - return 15.0 - 5: - return 1e-2 + var sdf_index: int = get_option_index(0) + match sdf_index: + 0, 1: + match port: + 0, 5, 6: + return Vector3(0, 0, 0) + 1: + return Vector3(0 ,0 ,-1) + 2: + return 15 + 3: + return 15.0 + 7: + return Vector3(0.3, 0.3, 0.3) + _: + return 1e-2 + 2, 3: + match port: + 0, 5, 6: + return Vector3(0, 0, 0) + 1: + return Vector3(0 ,0 ,-1) + 2: + return 15 + 3: + return 15.0 + 4: + return 1e-2 + 7: + return .5 if sdf_index == 2 else 1.0 + _: + return .3 _: - return null + match port: + 0, 5, 6: + return Vector3(0, 0, 0) + 1: + return Vector3(0 ,0 ,-1) + 2: + return 15 + 3: + return 15.0 + 4: + return 1e-2 + 7: + return 0.1 + _: + return 0.4 + +func _get_property_count() -> int: + return 1 + +func _get_property_default_index(index: int) -> int: + return 0 + +func _get_property_name(index: int) -> String: + return "SDF" + +func _get_property_options(index: int) -> PackedStringArray: + return ["SDBox", "SDSphere", "SDCapsule", "SDCylinder", "SDTorus"] func _get_output_port_count() -> int: return 1 @@ -65,19 +183,57 @@ func _get_output_port_type(port: int) -> PortType: return PORT_TYPE_SCALAR func _get_global_code(mode: Shader.Mode) -> String: - var code: String = preload("RayMarch.gdshaderinc").code + var code: String + var sdf_index: int = get_option_index(0) + code = preload("RayMarchRotation.gdshaderinc").code + match sdf_index: + 0: + code += preload("SDBox.gdshaderinc").code + 1: + code += preload("SDSphere.gdshaderinc").code + 2: + code += preload("SDCapsule.gdshaderinc").code + 3: + code += preload("SDCylinder.gdshaderinc").code + _: + code += preload("SDTorus.gdshaderinc").code return code func _get_code(input_vars: Array[String], output_vars: Array[String], mode: Shader.Mode, type: VisualShader.Type) -> String: - var signed_distance: String = "0.0" + var sdf_index: int = get_option_index(0) + var ray_origin: String = input_vars[0] + var ray_direction: String = input_vars[1] + var max_steps: String = input_vars[2] + var max_dist: String = input_vars[3] + var dist_threshold: String = input_vars[4] - if input_vars[0]: - signed_distance = input_vars[0] + match sdf_index: + 0: + var cube_pos: String = input_vars[5] + var eulers: String = input_vars[6] + var size: String = input_vars[7] + return output_vars[0] + " = ray_march_sd_box(%s, %s, %s, %s, %s, %s, %s, %s);" % [ray_origin, ray_direction, max_steps, max_dist, dist_threshold, cube_pos, eulers, size] + 1: + var sphere_pos: String = input_vars[5] + var eulers: String = input_vars[6] + var scale: String = input_vars[7] + return output_vars[0] + " = ray_march_sd_sphere(%s, %s, %s, %s, %s, %s, %s, %s);" % [ray_origin, ray_direction, max_steps, max_dist, dist_threshold, sphere_pos, eulers, scale] + 2: + var capsule_pos: String = input_vars[5] + var capsule_eulers: String = input_vars[6] + var capsule_height: String = input_vars[7] + var capsule_radius: String = input_vars[8] + return output_vars[0] + " = ray_march_sd_capsule(%s, %s, %s, %s, %s, %s, %s, %s, %s);" % [ray_origin, ray_direction, max_steps, max_dist, dist_threshold, capsule_pos, capsule_height, capsule_radius, capsule_eulers] + 3: + var cylinder_pos: String = input_vars[5] + var cylinder_eulers: String = input_vars[6] + var cylinder_height: String = input_vars[7] + var cylinder_radius: String = input_vars[8] + return output_vars[0] + " = ray_march_sd_cylinder(%s, %s, %s, %s, %s, %s, %s, %s, %s);" % [ray_origin, ray_direction, max_steps, max_dist, dist_threshold, cylinder_pos, cylinder_height, cylinder_radius, cylinder_eulers] + _: + var torus_pos: String = input_vars[5] + var eulers: String = input_vars[6] + var small_radius: String = input_vars[7] + var big_radius: String = input_vars[8] + return output_vars[0] + " = ray_march_sd_torus(%s, %s, %s, %s, %s, %s, %s, %s, %s);" % [ray_origin, ray_direction, max_steps, max_dist, dist_threshold, torus_pos, eulers, small_radius, big_radius] - var ray_origin: String = input_vars[1] - var ray_direction: String = input_vars[2] - var max_steps: String = input_vars[3] - var max_dist: String = input_vars[4] - var dist_threshold: String = input_vars[5] - - return output_vars[0] + " = ray_march(%s, %s, %s, %s, %s, %s);" % [ray_origin, ray_direction, max_steps, max_dist, dist_threshold, signed_distance] diff --git a/addons/ShaderLib/RayMarching/RayMarch.gdshaderinc b/addons/ShaderLib/RayMarching/RayMarchCustom.gdshaderinc similarity index 54% rename from addons/ShaderLib/RayMarching/RayMarch.gdshaderinc rename to addons/ShaderLib/RayMarching/RayMarchCustom.gdshaderinc index c24a18f..1a46c9e 100644 --- a/addons/ShaderLib/RayMarching/RayMarch.gdshaderinc +++ b/addons/ShaderLib/RayMarching/RayMarchCustom.gdshaderinc @@ -1,11 +1,18 @@ -float ray_march(vec3 ray_origin, vec3 ray_dir, int max_steps, float max_dist, float dist_threshold, float signed_dist) { +float sdf_custom(vec3 p) { + // Basic example of Sphere SDF with radius of .3 + // Put your custom logic here + float radius = .3; + return length(p) - radius; +} + +float ray_march_custom(vec3 ray_origin, vec3 ray_dir, int max_steps, float max_dist, float dist_threshold) { ray_dir = normalize(ray_dir); dist_threshold = abs(dist_threshold); float dist_from_origin = 0.; float dist_to_surface; for(int i = 0; i < max_steps; i++) { vec3 point = ray_origin + dist_from_origin * ray_dir; - dist_to_surface = signed_dist; + dist_to_surface = sdf_custom(point); dist_from_origin += dist_to_surface; if(dist_to_surface < dist_threshold || dist_to_surface > max_dist) break; diff --git a/addons/ShaderLib/RayMarching/RayMarchRotation.gdshaderinc b/addons/ShaderLib/RayMarching/RayMarchRotation.gdshaderinc new file mode 100644 index 0000000..6f88c7e --- /dev/null +++ b/addons/ShaderLib/RayMarching/RayMarchRotation.gdshaderinc @@ -0,0 +1,7 @@ +mat2 rm_rotation(float angle) { + angle = -angle * (3.1415926 / 180.); + return mat2( + vec2(cos(angle), -sin(angle)), + vec2(sin(angle), cos(angle)) + ); +} diff --git a/addons/ShaderLib/RayMarching/SDBox.gdshaderinc b/addons/ShaderLib/RayMarching/SDBox.gdshaderinc new file mode 100644 index 0000000..3da6be7 --- /dev/null +++ b/addons/ShaderLib/RayMarching/SDBox.gdshaderinc @@ -0,0 +1,21 @@ +float sd_box(vec3 point, vec3 size, vec3 eulers) { + point.yz *= rm_rotation(eulers.x); + point.xy *= rm_rotation(eulers.z); + point.xz *= rm_rotation(-eulers.y); + return length(max(abs(point) - size, vec3(0))); +} + +float ray_march_sd_box(vec3 ray_origin, vec3 ray_dir, int max_steps, float max_dist, float dist_threshold, vec3 cube_pos, vec3 eulers, vec3 size) { + ray_dir = normalize(ray_dir); + dist_threshold = abs(dist_threshold); + float dist_from_origin = 0.; + float dist_to_surface; + for(int i = 0; i < max_steps; i++) { + vec3 point = ray_origin + dist_from_origin * ray_dir; + dist_to_surface = sd_box(point - cube_pos, size, eulers); + dist_from_origin += dist_to_surface; + if(dist_to_surface < dist_threshold || dist_to_surface > max_dist) + break; + } + return dist_from_origin; +} \ No newline at end of file diff --git a/addons/ShaderLib/RayMarching/SDCapsule.gdshaderinc b/addons/ShaderLib/RayMarching/SDCapsule.gdshaderinc new file mode 100644 index 0000000..75d56d9 --- /dev/null +++ b/addons/ShaderLib/RayMarching/SDCapsule.gdshaderinc @@ -0,0 +1,33 @@ +float sd_capsule(vec3 point, vec3 capsule_pos, float height, float radius, vec3 eulers) { + vec3 orientation = vec3(0, 1, 0); + orientation.yz *= rm_rotation(eulers.x); + orientation.xy *= rm_rotation(eulers.z); + orientation.xz *= rm_rotation(-eulers.y); + + vec3 top_point = point + orientation * (height * .5); + vec3 bottom_point = point - orientation * (height * .5); + + vec3 height_vector = bottom_point - top_point; + vec3 top_distance = capsule_pos - top_point; + + float t = dot(height_vector, top_distance) / dot(height_vector, height_vector); + t = clamp(t, 0., 1.); + vec3 hit_point = top_point + t * height_vector; + + return length(capsule_pos - hit_point) - radius; +} + +float ray_march_sd_capsule(vec3 ray_origin, vec3 ray_dir, int max_steps, float max_dist, float dist_threshold, vec3 capsule_pos, float capsule_height, float capsule_radius, vec3 eulers) { + ray_dir = normalize(ray_dir); + dist_threshold = abs(dist_threshold); + float dist_from_origin = 0.; + float dist_to_surface; + for(int i = 0; i < max_steps; i++) { + vec3 point = ray_origin + dist_from_origin * ray_dir; + dist_to_surface = sd_capsule(point, capsule_pos, capsule_height, capsule_radius, eulers); + dist_from_origin += dist_to_surface; + if(dist_to_surface < dist_threshold || dist_to_surface > max_dist) + break; + } + return dist_from_origin; +} \ No newline at end of file diff --git a/addons/ShaderLib/RayMarching/SDCylinder.gdshaderinc b/addons/ShaderLib/RayMarching/SDCylinder.gdshaderinc new file mode 100644 index 0000000..0b5316e --- /dev/null +++ b/addons/ShaderLib/RayMarching/SDCylinder.gdshaderinc @@ -0,0 +1,37 @@ +float sd_cylinder(vec3 point, vec3 cylinder_pos, float height, float radius, vec3 eulers) { + vec3 orientation = vec3(0, 1, 0); + orientation.yz *= rm_rotation(eulers.x); + orientation.xy *= rm_rotation(eulers.z); + orientation.xz *= rm_rotation(-eulers.y); + + vec3 top_point = point + orientation * (height * .5); + vec3 bottom_point = point - orientation * (height * .5); + + vec3 height_vector = bottom_point - top_point; + vec3 top_distance = cylinder_pos - top_point; + + float t = dot(height_vector, top_distance) / dot(height_vector, height_vector); + vec3 hit_point = top_point + t * height_vector; + + float x = length(cylinder_pos - hit_point) - radius; + float y = (abs(t - .5) - .5) * length(height_vector); + float e = length(max(vec2(x, y), 0)); + float i = min(max(x, y), 0.); + + return e + i; +} + +float ray_march_sd_cylinder(vec3 ray_origin, vec3 ray_dir, int max_steps, float max_dist, float dist_threshold, vec3 cylinder_pos, float cylinder_height, float cylinder_radius, vec3 eulers) { + ray_dir = normalize(ray_dir); + dist_threshold = abs(dist_threshold); + float dist_from_origin = 0.; + float dist_to_surface; + for(int i = 0; i < max_steps; i++) { + vec3 point = ray_origin + dist_from_origin * ray_dir; + dist_to_surface = sd_cylinder(point, cylinder_pos, cylinder_height, cylinder_radius, eulers); + dist_from_origin += dist_to_surface; + if(dist_to_surface < dist_threshold || dist_to_surface > max_dist) + break; + } + return dist_from_origin; +} \ No newline at end of file diff --git a/addons/ShaderLib/RayMarching/SDSphere.gdshaderinc b/addons/ShaderLib/RayMarching/SDSphere.gdshaderinc new file mode 100644 index 0000000..7319423 --- /dev/null +++ b/addons/ShaderLib/RayMarching/SDSphere.gdshaderinc @@ -0,0 +1,23 @@ +float sd_sphere(vec3 point, vec3 eulers, vec3 scale) { + float radius = 1.; + point.yz *= rm_rotation(eulers.x); + point.xy *= rm_rotation(eulers.z); + point.xz *= rm_rotation(-eulers.y); + point /= scale; + return (length(point) - radius) * min(scale.x, min(scale.y, scale.z)); +} + +float ray_march_sd_sphere(vec3 ray_origin, vec3 ray_dir, int max_steps, float max_dist, float dist_threshold, vec3 sphere_pos, vec3 eulers, vec3 scale) { + ray_dir = normalize(ray_dir); + dist_threshold = abs(dist_threshold); + float dist_from_origin = 0.; + float dist_to_surface; + for(int i = 0; i < max_steps; i++) { + vec3 point = ray_origin + dist_from_origin * ray_dir; + dist_to_surface = sd_sphere(point - sphere_pos, eulers, scale); + dist_from_origin += dist_to_surface; + if(dist_to_surface < dist_threshold || dist_to_surface > max_dist) + break; + } + return dist_from_origin; +} \ No newline at end of file diff --git a/addons/ShaderLib/RayMarching/SDTorus.gdshaderinc b/addons/ShaderLib/RayMarching/SDTorus.gdshaderinc new file mode 100644 index 0000000..37c9746 --- /dev/null +++ b/addons/ShaderLib/RayMarching/SDTorus.gdshaderinc @@ -0,0 +1,21 @@ +float sd_torus(vec3 point, float small_radius, float big_radius, vec3 eulers) { + point.yz *= rm_rotation(eulers.x); + point.xy *= rm_rotation(eulers.z); + point.xz *= rm_rotation(-eulers.y); + return length(vec2(length(point.xz) - big_radius, point.y)) - small_radius; +} + +float ray_march_sd_torus(vec3 ray_origin, vec3 ray_dir, int max_steps, float max_dist, float dist_threshold, vec3 torus_pos, vec3 eulers, float small_radius, float big_radius) { + ray_dir = normalize(ray_dir); + dist_threshold = abs(dist_threshold); + float dist_from_origin = 0.; + float dist_to_surface; + for(int i = 0; i < max_steps; i++) { + vec3 point = ray_origin + dist_from_origin * ray_dir; + dist_to_surface = sd_torus(point - torus_pos, small_radius, big_radius, eulers); + dist_from_origin += dist_to_surface; + if(dist_to_surface < dist_threshold || dist_to_surface > max_dist) + break; + } + return dist_from_origin; +} \ No newline at end of file diff --git a/documentation/Nodes/RayMarching/RayMarch.md b/documentation/Nodes/RayMarching/RayMarch.md index e7ce218..b95dc44 100644 --- a/documentation/Nodes/RayMarching/RayMarch.md +++ b/documentation/Nodes/RayMarching/RayMarch.md @@ -52,8 +52,8 @@ This node is only simple ray marching example, the true power of raymarching can The default location can be found at
`res://addons/ShaderLib/RayMarching/RayMarchCustom.gdshaderinc` -You can copy the code from `RayMarchCustom.gdshaderinc` and then create a Global Expression/i> node and paste it in your visual shader.
-![Global Expression Node](GlobalExpression.jpg)
-Lastly you also need to create an Expression/i> node, define required input and output parameters and call the custom ray marching function as below.
-![Expression Node](Expression.jpg) +You can copy the code from `RayMarchCustom.gdshaderinc` and then create a Global Expression node and paste it in your visual shader.

+![Global Expression Node](GlobalExpression.jpg)

+Lastly you also need to create an Expression node, define required input and output parameters and call the custom ray marching function as below.

+![Expression Node](Expression.jpg)
___