Equirectangular view in Godot

Godot Version

Godot 4.3

Question

So i wanted to create a equirectangular view using 6 cameras forming a cube like this:


and a shader that will stich output from those cameras into a equirectangular view that can be for example shown as a 360 image or a video. But i can’t find any resources or tutorials that will explain how to do that.

There is no pipeline to directly get all cameras within one shader. I would create a viewport for each camera. Get an image from each viewport. Then pass the images into a shader to stich them together.

1 Like

Every camera already has a viewport, the problem is stiching them together.

I dont have a lot of experience on how the images should be segmented and distorted into the final texture. but my naive response would be to segment the view-port into regions on the 2D plane and test each fragment location with the region then apply a sampled pixel from the correct camera image.

to make your life easier i think the cameras can do 180 fov, so at most you need to do is a 3 region/image setup. (depending on the default vertical FOV).

but it all kind of depends on the output format you want. I think that detail can guide you. Equirectangular projection - Wikipedia

Did you write a sampler2D uniform to your shader? Can you assign that shader parameter a viewport texture?

I finally got it working! And I did that by writing a python script with help of ChatGPT that will take 6 screenshots from that cameras in the game and turned it into a equirectangular view and then i replicated this code into Godot shader language, then i applied this shader to a color rect under a SubViewport, that way i have a SubViewport with the 360 degree view of my game.

Here is the code if someone needed it (it may not be beautiful or optimized but it works):

shader_type canvas_item;

uniform sampler2D tex_front;
uniform sampler2D tex_back;
uniform sampler2D tex_left;
uniform sampler2D tex_right;
uniform sampler2D tex_top;
uniform sampler2D tex_bottom;

void fragment() {
	float yaw = UV.x * 2.0 * PI - PI;
	float pitch = UV.y * PI - PI / 2.0;
	pitch *= -1.0;
	
	float x = cos(pitch) * sin(yaw);
	float y = sin(pitch);
	float z = cos(pitch) * cos(yaw);
	
	float abs_x = abs(x);
	float abs_y = abs(y);
	float abs_z = abs(z);
	
	// 1 = front, 2 = left, 3 = right, 4 = back, 5 = top, 6 = bottom
	int face;
	float u;
	float v;
	if (abs_x >= abs_y && abs_x >= abs_z) {
		if (x > 0.0) {
			face = 3; // right
		}
		else {
			face = 2; // left
		}
		
		if (x > 0.0) {
			u = (-z / abs_x + 1.0) * 0.5;
		}
		else {
			u = (z / abs_x + 1.0) * 0.5;
		}
		
		v = ((-y / abs_x) + 1.0) * 0.5;
	}
	else if (abs_y >= abs_x && abs_y <= abs_z) {
		if (y > 0.0) {
			face = 5; // top
		}
		else {
			face = 6; // bottom
		}
		
		u = ((x / abs_y) + 1.0) * 0.5;
		if (y > 0.0) {
			v = (z / abs_y + 1.0) * 0.5;
		}
		else {
			v = (-z / abs_y + 1.0) * 0.5;
		}
	}
	else {
		if (z > 0.0) {
			face = 1; // front
		}
		else {
			face = 4; // back
		}
		
		u = ((x / abs_z) + 1.0) * 0.5;
		v = ((-y / abs_z) + 1.0) * 0.5;
		
		if (face == 4) { // back
			u = 1.0 - u;
		}
	}
	
	if (face == 1) { // front
		COLOR = texture(tex_front, vec2(u, v));
	} else if (face == 2) { // left
		COLOR = texture(tex_left, vec2(u, v));
	} else if (face == 3) { // right
		COLOR = texture(tex_right, vec2(u, v));
	} else if (face == 4) { // back
		COLOR = texture(tex_back, vec2(u, v));
	} else if (face == 5) { // top
		COLOR = texture(tex_top, vec2(u, v));
	} else if (face == 6) { // bottom
		COLOR = texture(tex_bottom, vec2(u, v));
	}
}

Note that it excpects that SubViewport textures have a aspect ratio of 1:1 and the output of the shader is 2:1.