Godot 4.3 Set Up a 3D Project, Skybox & Import Your Custom 3d Model from VSC
This guide teaches you how to create a 3D scene programmatically in Godot 4.3, complete with a custom skybox, using only code (no editor setup). We’ll explain core concepts like WorldEnvironment
, Camera3D
, and SkyMaterial.
Final Code Structure
Project/
├── scripts/
│ ├── main.gd # Root scene script
│ └── skybox.gd # Skybox configuration
└── textures/
└── default_sky.hdr # HDRI skybox texture
1. Setting Up the Main Scene (main.gd
)
🔹 Code
extends Node3D
func _ready():
# 1. Configure camera
var camera = Camera3D.new()
camera.position = Vector3(0, 1, 3) # Position (X, Y, Z)
camera.look_at(Vector3.ZERO) # Look at world origin
add_child(camera)
camera.make_current() # Activate this camera
# 2. Add skybox
var skybox = load("res://Scripts/skybox.gd").new()
add_child(skybox)
🔹 Explanation
-
Why We Need a Camera:
- Godot renders 3D scenes only through a camera.
camera.make_current()
ensures this is the active viewpoint.
-
Camera Positioning:
Vector3(0, 1, 3)
places the camera 3 units back and 1 unit up.look_at(Vector3.ZERO)
makes it point at the scene center.
-
Adding the Skybox:
skybox.gd
is loaded as aWorldEnvironment
node.- Added to the scene tree to affect global rendering.
2. Creating the Skybox (skybox.gd
)
🔹 Code
extends WorldEnvironment
func _init():
# 1. Create Environment resource
var env = Environment.new()
# 2. Configure background as sky
env.background_mode = Environment.BG_SKY
# 3. Create sky material with HDRI texture
var sky_material = PanoramaSkyMaterial.new()
sky_material.panorama = load("res://Textures/default_sky.hdr")
# 4. Assign material to Sky resource
var sky = Sky.new()
sky.sky_material = sky_material
env.sky = sky
# 5. Configure HDR exposure
var camera_attrs = CameraAttributesPhysical.new()
camera_attrs.exposure_multiplier = 1.5 # Brightness control
env.camera_attributes = camera_attrs
# 6. Apply settings to WorldEnvironment
environment = env
🔹 Step-by-Step Explanation
-
WorldEnvironment
Node:- Controls global rendering settings (sky, fog, ambient light).
- Only one can exist per scene.
-
BG_SKY
Mode:- Tells Godot to use a skybox instead of a solid color.
- (Common Mistake: Forgetting this results in a blank background).
-
PanoramaSkyMaterial
:- Uses an equirectangular (360°) HDRI texture.
.hdr
files provide realistic lighting data.
-
Sky
Resource:- Acts as a container for sky materials.
- Required even though we’re using a panorama.
-
CameraAttributesPhysical
:- Adjusts exposure for HDR lighting.
- Without this, your skybox might look too dark/bright.
3. Key Concepts
🔹 Why Use WorldEnvironment
?
- Global Settings: Affects the entire scene (not just specific objects).
- Sky Rendering: Provides background and ambient lighting.
- Post-Processing: Can add effects like fog or color grading.
🔹 HDRI Textures Explained
- What They Are: 360° images that capture real-world lighting.
- Why Use Them:
- Realistic backgrounds.
- Natural lighting for objects (no need for extra lights).
🔹 Camera Positioning Tips
look_at()
: Ensures the camera faces the target point.- Z-Axis: Moving backward uses positive Z values in Godot’s 3D space.
4. Common Issues & Fixes
🔸 Sky Not Appearing
- Check background_mode is
BG_SKY
. - Verify texture path in
PanoramaSkyMaterial
. - Ensure
exposure_multiplier
isn’t too low (try2.0
).
🔸 Purple/Black Skybox
- Texture Not Loaded: Confirm:
- File exists at
res://Textures/default_sky.hdr
. - No typos in path (case-sensitive on Linux/macOS).
- File exists at
🔸 Objects Too Dark
- Add ambient light in
skybox.gd
:env.ambient_light_color = Color(1, 1, 1) env.ambient_light_energy = 0.3
5. Understanding MeshInstance3D and Mesh (Beginner-Friendly Guide)
At this point, we’ve successfully set up:
- A camera so we can see the world.
- A skybox to provide a realistic background.
However, our scene still looks empty because there are no physical objects in it yet. To fix that, we’re going to add a cube.
🔹 5.1: What is a 3D Mesh?
Before we jump into the code, let’s break down a core concept: what a “mesh” is in 3D graphics.
What is a Mesh?
A mesh is the shape of a 3D object. Imagine a cube, a sphere, or a character model in a video game—all of these are made up of meshes.
In technical terms:
- A mesh is a collection of vertices (points) connected by edges (lines) , forming faces (triangles or polygons) .
- The GPU (Graphics Processing Unit) takes these points and renders them into a solid shape.
How Does Godot Handle Meshes?
Godot has prebuilt meshes that let us create 3D objects quickly, including:
BoxMesh
→ A cube.SphereMesh
→ A ball.CylinderMesh
→ A tube.
These meshes exist only as data —they don’t appear in the world unless we assign them to something.
🔹 5.2: What is a MeshInstance3D?
A MeshInstance3D is what actually makes a mesh visible in the 3D world.
Think of it like this:
- A mesh is just raw data (like a blueprint).
- A MeshInstance3D is a physical object that holds a mesh and makes it appear in the scene.
Analogy
If a mesh is like the blueprint for a house, then a MeshInstance3D is the actual house built from that blueprint.
A MeshInstance3D
does two important things :
- It holds a mesh, which gives it shape.
- It exists inside the 3D world, so the camera can see it.
🔹 5.3: Adding a Cube to the Scene
Now that we understand meshes and MeshInstance3D, let’s add a cube to the game world.
📝 Updated main.gd
Code
extends Node3D
var cube: MeshInstance3D # We declare a cube variable (empty for now)
# Animate the cube's rotation every frame
func _process(delta):
cube.rotate_y(delta * 0.5) # This makes the cube spin
func _ready():
# Create and configure the camera
var camera = Camera3D.new()
camera.position = Vector3(0, 1, 3) # Moves the camera up and back
camera.look_at(Vector3.ZERO) # Makes it face the center of the world
add_child(camera)
camera.make_current() # Set this as the active camera
# Create a new cube instance
cube = MeshInstance3D.new() # This is a new 3D object
cube.mesh = BoxMesh.new() # We assign a cube mesh (BoxMesh)
add_child(cube) # Add the cube to the scene so it appears
# Add world environment (Skybox)
var skybox = load("res://Scripts/skybox.gd").new()
add_child(skybox)
🔹 5.4: Breaking Down the Code
Let’s break this down step by step , making sure we understand every single line .
Step 1️⃣ – Creating a Camera
var camera = Camera3D.new()
camera.position = Vector3(0, 1, 3)
camera.look_at(Vector3.ZERO)
add_child(camera)
camera.make_current()
Camera3D.new()
→ Creates a new camera.camera.position = Vector3(0, 1, 3)
→ Moves it up and back so we can see the scene.camera.look_at(Vector3.ZERO)
→ Rotates the camera so it faces the center of the world .add_child(camera)
→ Adds it to the scene so it actually exists.camera.make_current()
→ Sets it as the active camera .
Step 2️⃣ – Creating a Cube
cube = MeshInstance3D.new() # Creates a new MeshInstance3D object
cube.mesh = BoxMesh.new() # Assigns a cube mesh
add_child(cube) # Adds the cube to the scene
This does three things :
MeshInstance3D.new()
→ Creates a new 3D object.BoxMesh.new()
→ Assigns it a cube shape.add_child(cube)
→ Adds the cube to the scene, making it visible .
Without add_child(cube)
, the cube would exist in memory but never appear .
Step 3️⃣ – Adding a Skybox
var skybox = load("res://Scripts/skybox.gd").new()
add_child(skybox)
This loads our skybox.gd
script, which adds a background to the scene .
🔹 5.5: Making the Cube Rotate
Right now, the cube is static . Let’s make it rotate.
func _process(delta):
cube.rotate_y(delta * 0.5) # Spins the cube around the Y-axis
Breaking This Down
_process(delta)
runs every frame in the game.cube.rotate_y(angle)
rotates the cube around the Y-axis .delta
ensures smooth rotation no matter the frame rate.
🔹 5.6: Running the Scene
If you run the game now , you should see:
- A skybox background 🌄
- A spinning cube in the center 🟦
- A camera showing the scene 🎥
🎉 Congratulations! You’ve created your first fully-coded 3D scene! 🚀
🔹 5.7: Troubleshooting & Common Issues
🔸 The Cube Doesn’t Appear
- Make sure the cube is added to the scene :
print("Cube added:", cube)
If this doesn’t print anything, add_child(cube)
might not be running.
- Check the camera position :
- If the camera is inside the cube, try moving it further back:
camera.position = Vector3(0, 2, 5)
🔸 The Cube Isn’t Rotating
- Check if
_process(delta)
is running :
print("Rotation running...")
If this doesn’t print, Godot isn’t calling _process()
. Try restarting the scene.
- Check for errors in the console :
- If
cube
isnull
, something is wrong with how it’s being assigned.
Step 6: Importing and Displaying a Custom 3D Model in Godot 4.3
At this stage, we have successfully established a 3D scene that includes a camera, skybox, and a rotating cube. However, the cube is merely a placeholder for testing purposes. Now, we will replace the cube with a custom 3D model, specifically whichever glb file you made in the previous guides. If you did not, go back and review or download one online.
🔹 Step 6.1: Understanding GLB Files and 3D Models in Godot
Before integrating our 000_Snowpuff.glb
model, it is essential to understand what a 3D model is and how Godot processes it.
What is a 3D Model?
A 3D model is a digital representation of an object in three-dimensional space, created using vertices (points) connected by edges and faces to form a shape.
These models can be created using 3D modeling software such as:
- Blender
- Autodesk Maya
- 3ds Max
- Cinema 4D
3D models can be textured, rigged (for animation), and include materials that define how they look in a scene.
What is a GLB File?
GLB (or GLTF Binary) is a universal 3D file format that is highly optimized for game engines like Godot. It is preferred because:
- It contains mesh data, textures, materials, and animations in a single file.
- It has smaller file sizes than other formats like
.obj
or.fbx
. - It is fully compatible with Godot without requiring external converters.
Our 000_Snowpuff.glb
file contains a fully textured 3D model of Snowpuff, which we will now integrate into the scene.
🔹 Step 6.2: Importing the Model into Godot
Step 1: Copy the Model into the Project Folder
Ensure that your 000_Snowpuff.glb
file is stored within the res://Models/ directory. Your project should now look like this:
Project/
├── Models/
│ └── 000_Snowpuff.glb # Custom 3D Model
├── Scripts/
│ ├── main.gd
│ └── skybox.gd
├── Textures/
│ └── default_sky.hdr
└── project.godot
Step 2: Let Godot Process the Model
Once the .glb
file is placed inside the Models/
directory, Godot will automatically detect it in the FileSystem panel .
To verify:
- Open Godot Editor .
- Navigate to res://Models/ in the FileSystem panel .
- Locate
000_Snowpuff.glb
and click on it.
At this stage, Godot will generate additional files , including:
- 000_Snowpuff.material → Stores material settings for the model.
- 000_Snowpuff.meshes → Stores mesh data.
- 000_Snowpuff.animations (if applicable).
These are automatically created and used when loading the .glb
file into the scene.
🔹 Step 6.3: Replacing the Cube with Snowpuff in main.gd
Now that our model is imported, we need to dynamically load and instantiate it through code in main.gd
.
📝 Updated main.gd
Code
extends Node3D
var snowpuff: Node3D
# Animate rotation
func _process(delta):
snowpuff.rotate_y(delta * 0.5)
func _ready():
# Configure camera
var camera = Camera3D.new()
camera.position = Vector3(0, 1, 3)
camera.look_at(Vector3.ZERO)
add_child(camera)
camera.make_current()
# Add a rotating snowpuff
var snowpuff_model = load("res://Models/000_Snowpuff.glb")
if snowpuff_model:
snowpuff = snowpuff_model.instantiate()
snowpuff.transform.origin = Vector3(0,0,0)
add_child(snowpuff)
# Add world environment
var skybox = load("res://Scripts/skybox.gd").new()
add_child(skybox)
🔹 Step 6.4: Breaking Down the Code
1️⃣ Loading the 3D Model
var snowpuff_scene = load("res://Models/000_Snowpuff.glb")
load("res://Models/000_Snowpuff.glb")
loads the 3D model file.- The file must be inside the “res://” directory , otherwise it won’t be found.
load()
simply loads the file into memory , but does not yet create an instance of it in the game.
2️⃣ Creating an Instance of the Model
if snowpuff_model:
snowpuff = snowpuff_model.instantiate()
.instantiate()
creates an actual instance of the model in the scene.- This is necessary because
.glb
files are scenes, not single objects . - If this step is skipped, the model will remain loaded in memory but will never appear in the scene .
3️⃣ Positioning the Model
snowpuff.transform.origin = Vector3(0, 0, 0)
- Moves the model to the center of the scene .
- If the model appears too high or too low , adjust the Y-axis:
snowpuff.transform.origin = Vector3(0, -0.5, 0)
4️⃣ Rotating the Model
func _process(delta):
if snowpuff:
snowpuff.rotate_y(delta * 0.5)
- The model rotates around the Y-axis over time.
delta
ensures smooth rotation at any frame rate .
🔹 Step 6.5: Running the Scene
After implementing these changes, running the game should now display:
✅ A skybox background 🌄
✅ A custom 3D model (Snowpuff) instead of a cube ❄️
✅ Smooth rotation animation 🔄
✅ A properly positioned and rendered model 🎨
🔹 Step 6.6: Troubleshooting Common Issues
🔸 The Model Doesn’t Appear
- Check if the file path is correct :
var snowpuff_model = load("res://Models/000_Snowpuff.glb")
The file must exist in the correct location.
- Make sure the model is instantiated :
if snowpuff_model:
print("Model loaded successfully")
else:
print("Failed to load model")
- Check if the model is too small or inside the floor :
- Try scaling it up:
snowpuff.scale = Vector3(2, 2, 2)
🔸 The Model is Too Dark
- Ensure there is lighting in the scene (Godot does not add lights by default).
- Add an ambient light source:
var light = DirectionalLight3D.new() light.light_energy = 2.0 add_child(light)