I’ve started implementing something new for G’MIC. Would you guess what is it about?

^ this is my first result^
I’ve started implementing something new for G’MIC. Would you guess what is it about?

^ this is my first result^
Sphere packing on the surface of sphere?
Nope.
Simulation of atomic nuclei?
Belated Easter Egg Hunt?
No and no.
It’s more about the rendering itself than the object type to be honest.
After some work, here is the result of the second iteration:

It starts looking good no? ![]()
Guess: how to make shadows look real?
We’re getting close!
Ray-tracing or something to do with marching? I want to have options for rendering though.
Like these:
Enable reflection?
Reflection depth level
Enable Shadow?
Overlay Wireframe? (With Wireframe color and thickness)
@Reptorian guessed it. I’m planning to add a (simple) raytracer into G’MIC, so that I can render more realistic 3D scenes, with physically-plausible light model, shadows, and reflections.
The example below has been implemented in less than 200 lines of G’MIC code, which is quite cool
I’ll post new images as I move forward in the implementation.
Stay tuned ![]()
i think you’re lagging behind by at least 2x
here’s global illumination (not 1975 illumination…) in 99 lines of code: smallpt: Global Illumination in 99 lines of C++
Yes, there is even a well known raytracer source code that can be written on a visiting card ::
But in my case, I just want something usable and maintainable for G’MIC, so I allow myself a bit more line of codes than that !
Third iteration. Now adding reflections (first-level only, no recursions).
I’ll stop there for today

Current version of the code (prototype, just ugly
), in case you want to play with it:
test_raytrace3d :
sphere3d 10,1 circles3d. 5 l. { s3d rand.. 0,255 n.. 128,255 a y } c3d. n3d. *3d. 500 r3d. 1,1,1,33
f=0
repeat 180 {
e "FRAME "$f
r3d[0] 1,1,1,2
++3d[0] 0,0,0
raytrace3d. 600,600
n. 0,255
# d q
w.
on. frame.png,$f
f+=1
rm.
}
#@cli raytrace3d : _width>0,_height>0
#@cli : Render selected 3D objects using raytracer.
raytrace3d : check "isint(${1=512}) && $1>0 && isint(${2=$1}) && $2>0"
e[^-1] "Render 3D object$? into a $1x$2 image."
foreach {
# Decompose object as a set of independent 3D primitives.
p3d 2 s3d
foreach[2,4] { r 3,{h/3},1,1,-1 permute cyzx }
1,1,1,17
eval "
nb_pts = i[#1,0];
nb_prims = i[#1,1];
off_p = off_c = off_a = 0;
repeat(nb_prims,p,
N = i[#3,off_p++];
N==5?( # Sphere
i0 = i[#3,off_p++]; i1 = i[#3,off_p++]; off_p+=3;
P0 = I[#2,i0];
P1 = I[#2,i1];
Pc = (P0 + P1)/2;
radius = norm(P1 - P0)/2;
RGB = I[#4,off_c++];
A = i[#5,off_a++];
da_push([5,Pc,radius,0,0,0,0,0,0,0,0,RGB,A]);
):(off_p+=N; ++off_c; ++off_a);
);
da_freeze()"
k.
# Render to image.
$1,$2,1,3,"*
begin(
P_camera = [ 0,0,-800 ];
P_light = [ 800,-800,-500 ];
RGB_background = [ 128,128,150 ];
);
t_intersect_sphere(P_ray,V_ray,P_sphere,r_sphere) = (
R2 = r_sphere^2;
L = P_sphere - P_ray;
L2 = dot(L,L);
is_in_sphere = L2<R2;
tca = dot(L,V_ray);
tca<0?inf:(
D2 = L2 - tca^2;
thc2 = R2 - D2;
thc2<0?inf:(tca + sqrt(thc2)*(is_in_sphere?1:-1));
);
);
V_ray = [ x - w/2,y - h/2,0 ] - P_camera;
V_ray/=norm(V_ray);
# Determine if the ray intersects a primitive.
p_min = t_min = inf;
repeat (h#0,p,
type_primitive = i[#0,p];
type_primitive==5?( # Sphere
P_sphere = [ i(#0,0,p,0,1), i(#0,0,p,0,2), i(#0,0,p,0,3) ]; r_sphere = i(#0,0,p,0,4);
t = t_intersect_sphere(P_camera,V_ray,P_sphere,r_sphere);
):(t = inf);
t<t_min?(p_min = p; t_min = t);
);
# Determine point color.
RGB = RGB_background;
!isinf(p_min)?(
type_primitive = i[#0,p_min];
RGB_primitive = [ i(#0,0,p_min,0,13), i(#0,0,p_min,0,14), i(#0,0,p_min,0,15) ];
P_intersection = P_camera + t_min*V_ray;
V_light = P_light - P_intersection;
V_light/=norm(V_light);
type_primitive==5?( # Sphere
P_sphere = [ i(#0,0,p_min,0,1), i(#0,0,p_min,0,2), i(#0,0,p_min,0,3) ];
V_normal = P_intersection - P_sphere;
);
V_normal/=norm(V_normal);
# Manage light source.
q_min = t_min = inf;
repeat (h#0,q,
type_primitive = i[#0,q];
type_primitive==5?( # Sphere
P_sphere = [ i(#0,0,q,0,1), i(#0,0,q,0,2), i(#0,0,q,0,3) ];
r_sphere = i(#0,0,q,0,4);
t = t_intersect_sphere(P_intersection,V_light,P_sphere,r_sphere);
):(t = inf);
t<t_min?(q_min = q; t_min = t; break());
);
k_ambient = 0.5;
RGB_ambient = RGB_primitive;
k_diffuse = k_specular = 0;
isinf(t_min)?( # Light source reach intersection point.
k_diffuse = 0.5;
c_diffuse = dot(V_normal,V_light);
RGB_diffuse = c_diffuse*RGB_primitive;
k_specular = 0.75;
V_specularV = P_camera - P_intersection;
V_specularV/=norm(V_specularV);
V_specularR = 2*dot(V_normal,V_light)*V_normal - V_light;
c_specular = max(0,dot(V_specularR,V_specularV))^20;
RGB_specular = c_specular*[ 255,255,255 ];
);
RGB_lighting = k_ambient*RGB_ambient + k_diffuse*RGB_diffuse + k_specular*RGB_specular;
# Manage reflection.
V_reflect = -2*dot(V_ray,V_normal)*V_normal + V_ray;
V_reflect/=norm(V_reflect);
q_min = t_min = inf;
repeat (h#0,q,
type_primitive = i[#0,q];
type_primitive==5?( # Sphere
P_sphere = [ i(#0,0,q,0,1), i(#0,0,q,0,2), i(#0,0,q,0,3) ];
r_sphere = i(#0,0,q,0,4);
t = t_intersect_sphere(P_intersection,V_reflect,P_sphere,r_sphere);
):(t = inf);
t<t_min?(q_min = q; t_min = t; break());
);
k_reflection = 0.25;
isinf(t_min)?(
RGB_reflection = RGB_background;
):(
RGB_reflection = [ i(#0,0,q_min,0,13), i(#0,0,q_min,0,14), i(#0,0,q_min,0,15) ];
);
# RGB = cut(RGB_light,0,255);
RGB = cut(lerp(RGB_lighting,RGB_reflection,k_reflection),0,255);
);
RGB"
k.
}
Finally, I had some time to implement recursive raytracing for better managing reflections.

What is funny is that also simplifies the code a bit, sot it’s shorter than previous version:
test_raytracing :
# sphere3d 10,1 circles3d. 5
curve3d "cos(3*t)","sin(4*t)","cos(t)",0,30,0,{2*pi},0 circles3d. 0.4
l. { s3d rand.. 0,255 n.. 64,255 a y }
c3d. n3d. *3d. 400 r3d. 1,1,1,33
f=0
repeat 180 {
e "FRAME "$f
r3d[0] 0,1,0,2
r3d[0] 0,0,1,4
+raytrace3d. 512,512
# d0. q
w.
on. frame.png,$f
f+=1
rm.
}
#@cli raytrace3d : _width>0,_height>0
#@cli : Render selected 3D objects using raytracer.
raytrace3d : check "isint(${1=512}) && $1>0 && isint(${2=$1}) && $2>0"
e[^-1] "Render 3D object$? into a $1x$2 image."
foreach {
# Decompose object as a set of independent 3D primitives.
p3d 2 s3d
foreach[2,4] { r 3,{h/3},1,1,-1 permute cyzx }
1,1,1,17
eval "
nb_pts = i[#1,0];
nb_prims = i[#1,1];
off_p = off_c = off_a = 0;
repeat(nb_prims,p,
N = i[#3,off_p++];
N==5?( # Sphere
i0 = i[#3,off_p++]; i1 = i[#3,off_p++]; off_p+=3;
P0 = I[#2,i0];
P1 = I[#2,i1];
Pc = (P0 + P1)/2;
radius = norm(P1 - P0)/2;
RGB = I[#4,off_c++];
A = i[#5,off_a++];
da_push([5,Pc,radius,0,0,0,0,0,0,0,0,RGB,A]);
):(off_p+=N; ++off_c; ++off_a);
);
da_freeze()"
k.
# Render to image using raytracing.
$1,$2,1,3,"*
begin(
P_camera = [ 0,0,-800 ];
P_light = [ 800,-800,-500 ];
);
RGB_background = lerp([ 32,48,96 ],[ 48,96,32 ],y/(h-1));
# Return parameter 't' corresponding to the intersection ray/sphere (return 'inf' if no intersection).
t_intersect_sphere(P_ray,V_ray,P_sphere,r_sphere) = (
R2 = r_sphere^2;
L = P_sphere - P_ray;
L2 = dot(L,L);
is_in_sphere = L2<R2;
tca = dot(L,V_ray);
tca<0?inf:(
D2 = L2 - tca^2;
thc2 = R2 - D2;
thc2<0?inf:(tca + sqrt(thc2)*(is_in_sphere?1:-1));
);
);
# Return RGB color corresponding to specified ray.
# P_ray: Ray origin point (vector3).
# V_ray: Ray direction vector (vector3).
# level: Maximum number of recursion levels for reflections (must be a constant>=1).
raytrace(P_ray,V_ray,level) = (
# Determine if the ray intersects a primitive.
p_min = t_min = inf;
repeat (h#0,p,
type_primitive = i[#0,p];
type_primitive==5?( # Sphere
P_sphere = [ i(#0,0,p,0,1), i(#0,0,p,0,2), i(#0,0,p,0,3) ]; r_sphere = i(#0,0,p,0,4);
t = t_intersect_sphere(P_ray,V_ray,P_sphere,r_sphere);
):(t = inf);
t<t_min?(p_min = p; t_min = t);
);
# Determine point color.
RGB_#level = RGB_background;
!isinf(p_min)?(
type_primitive = i[#0,p_min];
RGB_primitive = [ i(#0,0,p_min,0,13), i(#0,0,p_min,0,14), i(#0,0,p_min,0,15) ];
P_intersection = P_ray + t_min*V_ray;
V_light = P_light - P_intersection;
V_light/=norm(V_light);
type_primitive==5?( # Sphere
P_sphere = [ i(#0,0,p_min,0,1), i(#0,0,p_min,0,2), i(#0,0,p_min,0,3) ];
V_normal = P_intersection - P_sphere;
);
V_normal/=norm(V_normal);
# Manage light source.
P_intersection_eps = P_intersection + 1e-3*V_light;
q_min = t_min = inf;
repeat (h#0,q,
type_primitive = i[#0,q];
type_primitive==5?( # Sphere
P_sphere = [ i(#0,0,q,0,1), i(#0,0,q,0,2), i(#0,0,q,0,3) ];
r_sphere = i(#0,0,q,0,4);
t = t_intersect_sphere(P_intersection_eps,V_light,P_sphere,r_sphere);
):(t = inf);
t<t_min?(q_min = q; t_min = t; break());
);
k_ambient = 0.75;
RGB_ambient = RGB_primitive;
k_diffuse = k_specular = 0;
isinf(t_min)?( # Light source reach intersection point.
k_diffuse = 0.75;
c_diffuse = dot(V_normal,V_light);
RGB_diffuse = c_diffuse*RGB_primitive;
k_specular = 0.5;
V_specularV = P_ray - P_intersection;
V_specularV/=norm(V_specularV);
V_specularR = 2*dot(V_normal,V_light)*V_normal - V_light;
c_specular = max(0,dot(V_specularR,V_specularV))^20;
RGB_specular = c_specular*[ 255,255,255 ];
);
RGB_lighting_#level = k_ambient*RGB_ambient + k_diffuse*RGB_diffuse + k_specular*RGB_specular;
# Manage reflection.
level>0?(
const levelm1_#level = level# - 1;
V_reflect = -2*dot(V_ray,V_normal)*V_normal + V_ray;
V_reflect/=norm(V_reflect);
P_intersection_eps = P_intersection + 1e-3*V_reflect;
RGB_reflection_#level = raytrace(P_intersection_eps,V_reflect,levelm1_#level);
RGB_#level = 0.75*RGB_lighting_#level + 0.3*RGB_reflection_#level;
):(
RGB_#level = RGB_lighting_#level;
);
);
cut(RGB_#level,0,255);
);
V_ray = [ x - w/2,y - h/2,0 ] - P_camera;
V_ray/=norm(V_ray);
raytrace(P_camera,V_ray,1)"
k.
}
Next step: Manage other primitives than spheres: planes, triangles, quadrangles.
Also I want to try refraction, in order to manage transparent or semi-transparent objects.
Honestly, I ever thought raytracing was a tough stuff to do. I’m happy to discover it’s actually quite feasible (with limited features of course).
Nice,
will it have soft shadows, materials, bump-mapping, etc?
well it’s not like i will be able to do anything with it anyway lol.
Looks good. Pretty soon, Bouncing Balls will need an upgrade.
Forget about doing it in real time then ![]()
Reflections would be cool. Even better if you can use bitmap with transparency with objects that you would like reflections to exist. I fake it a bit using photokiller’s glaze plugin (via Shellout>XNView) but it’s not the best. Use this with my marbles renders. ![]()
Ah, you need to study vkdt. Put the processing pipe in the GPU…
for the off chance that you are keen, let me know. vkdt/src/pipe/raytrace.c at master · hanatos/vkdt · GitHub. glenn has been working on a libvkdt backend, though my intuition is that this would be quite a bit of overkill for gmic.
It would be cool if G’MIC could process Pov-Ray scene files. Really, really cool. With GPU processing, natch.