VisualShaderNodeCubemap in Godot – Complete Guide

Welcome to our tutorial on the VisualShaderNodeCubemap in Godot 4. This powerful node allows you to incorporate complex effects in your game by sampling cube maps, bringing your 3D scenes to life with dynamic skies or even reflections. By harnessing this feature, you open up a new dimension of visual effects that can elevate the quality and immersion of your game. Whether you’re just getting started with Godot or looking to deepen your shader knowledge, this tutorial will guide you through the ins and outs of the VisualShaderNodeCubemap, making it an accessible and exciting tool to add to your game development toolkit.

What is VisualShaderNodeCubemap?

The VisualShaderNodeCubemap is a node within the Godot engine that allows you to sample cubemaps within a visual shader graph. A cubemap is a collection of six square textures that represent the views along the positive and negative directions of the world’s axes. Typically used for skyboxes, environment maps, or dynamic reflections, the VisualShaderNodeCubemap is the interface to bring these textures into your shaders.

What is it for?

Cubemaps are essential for adding realism to 3D scenes, and the VisualShaderNodeCubemap provides a straightforward way to implement them. Through cube mapping, developers can create vast skies, realistic water reflections, and enhance the environmental lighting of their scenes. It is particularly useful for creating environmental effects that need to reflect the game world in real-time.

Why Should You Learn It?

Shader programming can initially seem intimidating, but visual shader nodes, such as the VisualShaderNodeCubemap, simplify the process by providing a visual representation of the shader’s logic. Learning to use this node effectively can enhance the visual fidelity of your projects remarkably. It is a valuable skill for game developers to master, as it can be the difference between a good game and a visually stunning one. Plus, with Godot’s rising popularity, getting comfortable with its shader system is a wise investment for any aspiring or current game developer.

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Setting up the Shader with VisualShaderNodeCubemap

In this part of the tutorial, we’ll go through the process of creating and applying a simple cubemap to a skydome. We will begin by setting up our VisualShader.

var shader = VisualShader.new()
var cubemap_node = VisualShaderNodeCubemap.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, cubemap_node, Vector2(10, 5))

This code snippet creates a new visual shader, adds a new VisualShaderNodeCubemap instance to it, and positions the node on the shader graph. With the shader and cubemap node set up, we can now proceed to load our cubemap texture.

var cubemap = preload("res://path_to_your_cubemap.tres")
cubemap_node.set_cubemap(cubemap)

This loads the cubemap resource from a path in your project and sets it in the cubemap node. If you haven’t created a cubemap resource yet, you can do so by importing six textures that represent each side of the cube in your project.

Sampling the Cubemap in the Shader

With your VisualShaderNodeCubemap configured with a cubemap, you need to sample it to actually use it in your shader. To do this, you connect the cubemap node’s output to a shader output, such as emission or albedo in the Fragment function. Here are a few examples:

var texture_node = VisualShaderNodeTexture.new()
var output_node = VisualShaderNodeOutput.new()

shader.add_node(VisualShader.TYPE_FRAGMENT, texture_node, Vector2(70, 5))
shader.add_node(VisualShader.TYPE_FRAGMENT, output_node, Vector2(130, 5))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, cubemap_node.get_output_port_index("cubemap"), texture_node.get_input_port_index("source"))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, texture_node.get_output_port_index("rgb"), output_node.get_input_port_index("emission"))

The above creates a texture node and an output node, placing them in the respective positions. It then connects the cubemap node’s output to the texture node, effectively sampling the cubemap and using its RGB values for the output node’s emission, which will render the cubemap directly onto your skydome.

Alternatively, if you wish to use the cubemap for reflective effects on an object’s surface, you would use the object’s normal and reflection vector to sample the cubemap.

var uv_node = VisualShaderNodeUV.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, uv_node, Vector2(10, 50))

var reflect_node = VisualShaderNodeReflect.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, reflect_node, Vector2(50, 50))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, uv_node.get_output_port_index("uv"), reflect_node.get_input_port_index("normal"))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, reflect_node.get_output_port_index("reflection"), texture_node.get_input_port_index("uv"))

This code uses the UV coordinates and the built-in reflection calculation to get a reflective vector that is used to sample the cubemap for a reflection effect. By tweaking these connections and nodes, you can create various materials such as a reflective water surface, or shiny metallic objects.Next, let’s look into optimizing our shader to work better under different lighting conditions and scene setups. We’ll add a few more nodes to modify how the cubemap affects the material surface.

To enhance the realism of our reflective material, we can factor in the camera angle, creating a fresnel effect. This effect causes surfaces to reflect more light as the viewing angle increases.

var fresnel_node = VisualShaderNodeFresnel.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, fresnel_node, Vector2(90, 50))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, uv_node.get_output_port_index("uv"), fresnel_node.get_input_port_index("normal"))

Here we’re adding a VisualShaderNodeFresnel to our shader graph and connecting it to the UV node to calculate the fresnel effect based on the normal and viewing angle.

Adjusting the glossiness or roughness gives us control over how sharp or blurred the reflections will be.

var scalar_uniform = VisualShaderNodeScalarUniform.new()
scalar_uniform.set_hint_range(0.0, 1.0)
shader.add_node(VisualShader.TYPE_FRAGMENT, scalar_uniform, Vector2(30, 90))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, scalar_uniform.get_output_port_index("scalar"), texture_node.get_input_port_index("color"))

In the code snippet above, a scalar uniform node is used to control the glossiness dynamically. By connecting it to the color input of the texture node, the node’s output acts as a multiplier to the cubemap’s sampling.

To change the intensity of the cubemap reflection, we add a scalar uniform and connect it to a multiply node, which then factors into the shader’s output.

var multiply_node = VisualShaderNodeScalarOp.new()
multiply_node.set_operator(VisualShaderNodeScalarOp.Operator.MUL)
shader.add_node(VisualShader.TYPE_FRAGMENT, multiply_node, Vector2(110, 90))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, fresnel_node.get_output_port_index("fresnel"), multiply_node.get_input_port_index(0))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, scalar_uniform.get_output_port_index("scalar"), multiply_node.get_input_port_index(1))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, multiply_node.get_output_port_index("result"), output_node.get_input_port_index("emission"))

By combining the fresnel effect with the scalar uniform, the multiply node scales the intensity of the reflection based on the viewing angle, creating a more natural appearance.

If we are interested in enhancing the cubemap effect with the ambient light in the scene, we can add an ambient light node and combine it with our cubemap reflections.

var ambient_light_node = VisualShaderNodeAmbientLight.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, ambient_light_node, Vector2(50, 130))

var add_node = VisualShaderNodeVectorOp.new()
add_node.set_operator(VisualShaderNodeVectorOp.Operator.ADD)
shader.add_node(VisualShader.TYPE_FRAGMENT, add_node, Vector2(130, 130))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, texture_node.get_output_port_index("rgb"), add_node.get_input_port_index(0))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, ambient_light_node.get_output_port_index("color"), add_node.get_input_port_index(1))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, add_node.get_output_port_index("result"), output_node.get_input_port_index("emission"))

By adding ambient light to our cubemap reflection, we make the material better respond to the surrounding light conditions, making our environment appear more connected and cohesive.

Now we have a set of additional nodes that enhance our basic cubemap sampling, yielding much more sophisticated results. The use of nodes like Fresnel, ScalarUniform, Multiply, and AmbientLight changes how the cubemap texture interacts with light and the viewing angle, bringing us closer to a realistic and dynamic material.To further refine our material, we can manage the cubemap’s impact based on the distance in the scene. This technique is useful for creating effects like fog or atmospheric perspective.

var depth_node = VisualShaderNodeDepth.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, depth_node, Vector2(30, 170))

var linear_interp_node = VisualShaderNodeScalarInterp.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, linear_interp_node, Vector2(90, 170))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, depth_node.get_output_port_index("depth"), linear_interp_node.get_input_port_index(1))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, scalar_uniform.get_output_port_index("scalar"), linear_interp_node.get_input_port_index(2))

This code snippet introduces depth-based effects, utilizing a `VisualShaderNodeDepth` to assess the scene’s depth and a `VisualShaderNodeScalarInterp` to interpolate between two values based on that depth. By adjusting the `VisualShaderNodeScalarInterp` parameters, the effect’s strength can be precisely controlled with respect to the distance from the camera.

Next, we can add color correction to the cubemap reflections to match or contrast the scene’s ambient color tone.

var color_correction_node = VisualShaderNodeColorFunc.new()
color_correction_node.set_function(VisualShaderNodeColorFunc.Function.CONTRAST)
shader.add_node(VisualShader.TYPE_FRAGMENT, color_correction_node, Vector2(130, 210))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, texture_node.get_output_port_index("rgb"), color_correction_node.get_input_port_index("rgb"))

Here, the reflections are passed through a `VisualShaderNodeColorFunc` that adjusts the contrast of the cubemap colors. Color correction nodes can transform the visual feeling of materials, adding warmth, coolness, or vibrancy.

To give the appearance of motion or changing light conditions, we can add a time-based effect to the cubemap sampling.

var time_node = VisualShaderNodeTime.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, time_node, Vector2(10, 250))

var add_time_node = VisualShaderNodeScalarOp.new()
add_time_node.set_operator(VisualShaderNodeScalarOp.Operator.ADD)
shader.add_node(VisualShader.TYPE_FRAGMENT, add_time_node, Vector2(70, 250))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, time_node.get_output_port_index("time"), add_time_node.get_input_port_index(0))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, scalar_uniform.get_output_port_index("scalar"), add_time_node.get_input_port_index(1))

This block of code employs `VisualShaderNodeTime` to continuously get the shader’s running time and `VisualShaderNodeScalarOp` to add a value to it. By linking the time node’s output to other nodes, you can control animations or transformations in the shader’s variables based on time, like the rotation of a skybox or the pulsing of light reflections.

Finally, we should consider resource optimization by creating a shader parameter that determines whether the cubemap effect should be processed or not, saving performance if necessary.

var use_cubemap_node = VisualShaderNodeSwitch.new()
shader.add_node(VisualShader.TYPE_FRAGMENT, use_cubemap_node, Vector2(50, 290))

shader.connect_nodes(VisualShader.TYPE_FRAGMENT, texture_node.get_output_port_index("rgb"), use_cubemap_node.get_input_port_index(1))
shader.connect_nodes(VisualShader.TYPE_FRAGMENT, output_node.get_output_port_index("emission"), use_cubemap_node.get_input_port_index(0))

We introduce `VisualShaderNodeSwitch`, which acts as a conditional check on whether cubemap effects should be applied. By exposing a boolean uniform variable, we can toggle the cubemap effects in real-time, offering better performance when the full effects are not necessary.

These additional techniques demonstrate how we can customize and extend the VisualShaderNodeCubemap’s functionality. By leveraging Godot’s visual shaders, even complex visual effects become manageable, allowing for highly immersive and responsive game environments. As with any shader work, the key is to experiment with different nodes and connections to achieve the desired visual outcome, and optimize the shader for the best balance between aesthetics and performance.

Where to Go Next in Your Game Development Journey

Mastering VisualShaderNodeCubemap in Godot 4 is just the beginning. There’s so much more to explore and create within the realm of game development using Godot! Our comprehensive Godot Game Development Mini-Degree is designed to take you further, providing an extensive collection of courses covering essential topics like 2D and 3D game creation, GDScript programming, UI systems, and various game mechanics. Suitable for beginners and flexible enough for learning on any device, this Mini-Degree is a gateway to building your own portfolio of real Godot projects.

At Zenva, we’re passionate about helping you turn your game development dreams into reality. With our broad collection of Godot courses, you have access to over 250 high-quality courses designed to boost your skills and career. Whether you’re just starting out or ready to take your expertise to the next level, join us to learn coding, create games, and earn certificates that pave the way for a future in game development. Let’s continue crafting incredible gaming experiences together!

Conclusion

Delving into the depths of the VisualShaderNodeCubemap in Godot 4 has unlocked a wealth of possibilities for enhancing your games with captivating visual effects. As we’ve seen, the power of this tool lies in its ability to transform your 3D environments, creating immersive experiences that captivate players. Remember, every node you master is a step closer to realizing the full potential of your creativity in game development.

We at Zenva are excited to accompany you on this journey of learning and discovery. With the Godot Game Development Mini-Degree, you have the perfect companion to expand your skills and bring your unique game ideas to life. Keep building, keep learning, and most importantly, have fun creating! Your next groundbreaking game awaits your touch, and we can’t wait to see what you create next.

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.