Voxelbox

Voxel traversal through a 3D texture, designed to be applied to the default 1 x 1 x 1 cube. Owes much to this implementation. Possible improvements include per-voxel texturing and ambient occlusion for corners.

Shader code
shader_type spatial;
// Render back faces to prevent culling when camera is inside the mesh
render_mode cull_front;

uniform sampler3D voxels : source_color, filter_nearest, repeat_disable;
uniform int max_steps : hint_range(8, 256) = 32;

// https://iquilezles.org/articles/boxfunctions/
float box_intersection(vec3 ray_origin, vec3 ray_direction, vec3 size, out vec3 normal) {
	vec3 m = 1.0 / ray_direction;
	vec3 n = m * ray_origin;
	vec3 k = abs(m) * size;
	vec3 t0 = -n - k;
	normal = -sign(ray_direction) * step(t0.yzx, t0.xyz) * step(t0.zxy, t0.xyz);
	return max(max(t0.x, t0.y), t0.z);
}

varying mat4 modelview_matrix;
varying mat4 inv_modelview_matrix;

void vertex() {
	// Rotate by -90 degrees for z-up convention
	modelview_matrix = VIEW_MATRIX * MODEL_MATRIX * mat4(
		vec4(1, 0, 0, 0),
		vec4(0, 0, 1, 0),
		vec4(0, -1, 0, 0),
		vec4(0, 0, 0, 1)
	);
	inv_modelview_matrix = inverse(modelview_matrix);
	
	// Use front faces for shadow pass
	if (IN_SHADOW_PASS) VERTEX *= -1.0;
}

void fragment() {
	// Ray in model space
	vec3 ray_origin = inv_modelview_matrix[3].xyz;
	vec3 ray_direction = normalize((inv_modelview_matrix * vec4(-VIEW, 0.0)).xyz);
	vec3 ray_sign = sign(ray_direction);
	
	// Transform ray to 3D texture space
	ivec3 i_grid_size = textureSize(voxels, 0);
	vec3 f_grid_size = vec3(i_grid_size);
	ray_origin += 0.5;
	ray_origin *= f_grid_size;
	ray_direction *= f_grid_size;
	
	// Find ray intersection with bounding box, or use camera position if we're inside
	float bounds = box_intersection(ray_origin - 0.5 * f_grid_size, ray_direction, vec3(0.5 * f_grid_size), NORMAL);
	float entry_t = max(bounds, 0.0);
	vec3 entry = ray_origin + ray_direction * entry_t;
	
	// p - texture sample point
	ivec3 p = ivec3(entry);
	p = clamp(p, ivec3(0), i_grid_size - 1);
	ivec3 delta_p = ivec3(ray_sign);
	
	// t - distance along ray
	float t = entry_t;
	vec3 delta_t = abs(1.0 / ray_direction);
	vec3 t_max = (ray_sign * (vec3(p) - entry) + (ray_sign * 0.5) + 0.5) * delta_t;
	
	bool hit = false;
	bool in_bounds = false;
	bool in_voxel = false;
	int axis;
	vec4 voxel;
	for (int i = 0; i < max_steps; i++) {
		voxel = texelFetch(voxels, p, 0);
		if (voxel.a > 0.0) {
			hit = true;
			in_bounds = i > 0;
			in_voxel = i == 0 && entry_t == 0.0;
			break;
		}
		
		if (t_max.x < t_max.y) {
			if (t_max.x < t_max.z) axis = 0;
			else axis = 2;
		}
		else {
			if (t_max.y < t_max.z) axis = 1;
			else axis = 2;
		}
		
		p[axis] += delta_p[axis];
		t_max[axis] += delta_t[axis];
		
		// If the current sample point is out of bounds, discard
		if (any(lessThan(p, ivec3(0))) || any(greaterThanEqual(p, i_grid_size))) discard;
	}
	if (!hit) discard;
	
	if (in_voxel) {
		// If hit point is inside a voxel, use camera-facing normal
		NORMAL = vec3(0.0, 0.0, 1.0);
		// Set small t value to prevent clipping
		t = 0.0001;
	}
	else {
		if (in_bounds) {
			// If hit point is in bounds calculate normal, otherwise default to bounding box normal
			NORMAL = vec3(0.0);
			NORMAL[axis] = -ray_sign[axis];
			t += t_max[axis] - delta_t[axis];
		}
		NORMAL = (modelview_matrix * vec4(NORMAL, 0.0)).xyz;
	}
	
	// Transform hit point from texture space to clip space
	vec3 local_pos = (ray_origin + ray_direction * t) / f_grid_size - 0.5;
	vec4 view_pos = modelview_matrix * vec4(local_pos, 1.0);
	vec4 clip_pos = PROJECTION_MATRIX * view_pos;
	clip_pos.xyz /= clip_pos.w;
	
	LIGHT_VERTEX = view_pos.xyz;
	DEPTH = clip_pos.z;
	
	// Surface properties
	ALBEDO.rgb = voxel.rgb;
}
Live Preview
Tags
raytracing, Volume, Voxel
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from tentabrobpy

guest

0 Comments
Oldest
Newest Most Voted