Merge pull request #10606 from tetrapod00/first-3d-update

Improve First 3D shader tutorial
This commit is contained in:
Matthew
2025-02-04 17:35:24 -05:00
committed by GitHub
2 changed files with 70 additions and 57 deletions

View File

@@ -812,6 +812,7 @@ There are two possible interpolation qualifiers:
| **smooth** | The value is interpolated in a perspective-correct fashion. This is the default.|
+-------------------+---------------------------------------------------------------------------------+
.. _doc_shading_language_uniforms:
Uniforms
--------

View File

@@ -63,20 +63,21 @@ Setting up
Add a new :ref:`MeshInstance3D <class_MeshInstance3D>` node to your scene.
In the inspector tab beside "Mesh" click "<empty>" and select "New PlaneMesh".
Then click on the image of a plane that appears.
In the inspector tab, set the MeshInstance3D's **Mesh** property to a new
:ref:`PlaneMesh <class_planemesh>` resource, by clicking on ``<empty>`` and
choosing **New PlaneMesh**. Then expand the resource by clicking on the image of
a plane that appears.
This adds a :ref:`PlaneMesh <class_planemesh>` to our scene.
This adds a plane to our scene.
Then, in the viewport, click in the upper left corner on the button that says
"Perspective". A menu will appear. In the middle of the menu are options for how
to display the scene. Select 'Display Wireframe'.
Then, in the viewport, click in the upper left corner on the **Perspective** button.
In the menu that appears, select **Display Wireframe**.
This will allow you to see the triangles making up the plane.
.. image:: img/plane.webp
Now set ``Subdivide Width`` and ``Subdivide Depth`` of the :ref:`PlaneMesh <class_planemesh>` to ``32``.
Now set **Subdivide Width** and **Subdivide Depth** of the :ref:`PlaneMesh <class_planemesh>` to ``32``.
.. image:: img/plane-sub-set.webp
@@ -87,12 +88,13 @@ and thus allow us to add more detail.
.. image:: img/plane-sub.webp
:ref:`PrimitiveMeshes <class_primitivemesh>`, like PlaneMesh, only have one
surface, so instead of an array of materials there is only one. Click
beside "Material" where it says "<empty>" and select "New ShaderMaterial".
Then click the sphere that appears.
surface, so instead of an array of materials there is only one. Set the
**Material** to a new ShaderMaterial, then expand the material by clicking on
the sphere that appears.
Now click beside "Shader" where it says "<empty>" and select "New Shader...". Leave
the default settings, give your shader a name and click "Create".
Now set the material's **Shader** to a new Shader by clicking ``<empty>`` and
select **New Shader...**. Leave the default settings, give your shader a name,
and click **Create**.
Click on the shader in the inspector, and the shader editor should now pop up. You
are ready to begin writing your first Spatial shader!
@@ -116,7 +118,7 @@ appear in the final scene. We will be using it to offset the height of each vert
and make our flat plane appear like a little terrain.
With nothing in the ``vertex()`` function, Godot will use its default vertex
shader. We can easily start to make changes by adding a single line:
shader. We can start to make changes by adding a single line:
.. code-block:: glsl
@@ -130,12 +132,12 @@ Adding this line, you should get an image like the one below.
Okay, let's unpack this. The ``y`` value of the ``VERTEX`` is being increased.
And we are passing the ``x`` and ``z`` components of the ``VERTEX`` as arguments
to ``cos`` and ``sin``; that gives us a wave-like appearance across the ``x``
and ``z`` axes.
to :ref:`cos() <shader_func_cos>` and :ref:`sin() <shader_func_sin>`; that gives
us a wave-like appearance across the ``x`` and ``z`` axes.
What we want to achieve is the look of little hills; after all. ``cos`` and
``sin`` already look kind of like hills. We do so by scaling the inputs to the
``cos`` and ``sin`` functions.
What we want to achieve is the look of little hills; after all. ``cos()`` and
``sin()`` already look kind of like hills. We do so by scaling the inputs to the
``cos()`` and ``sin()`` functions.
.. code-block:: glsl
@@ -166,30 +168,19 @@ shader, outside the ``vertex()`` function.
uniform sampler2D noise;
This will allow you to send a noise texture to the shader. Now look in the
inspector under your material. You should see a section called "Shader Params".
If you open it up, you'll see a section called "noise".
inspector under your material. You should see a section called **Shader Parameters**.
If you open it up, you'll see a parameter called "Noise".
Click beside it where it says "<empty>" and select "New NoiseTexture2D". Then in
your :ref:`NoiseTexture2D <class_noisetexture2D>` click beside where it says "Noise" and select "New
FastNoiseLite".
.. note:: :ref:`FastNoiseLite <class_fastnoiselite>` is used by the NoiseTexture2D to
generate a heightmap.
Set this **Noise** parameter to a new :ref:`NoiseTexture2D <class_noisetexture2D>`.
Then in your NoiseTexture2D, set its **Noise** property to a new
:ref:`FastNoiseLite <class_fastnoiselite>`. The FastNoiseLite class is used by
the NoiseTexture2D to generate a heightmap.
Once you set it up and should look like this.
.. image:: img/noise-set.webp
Now, access the noise texture using the ``texture()`` function. ``texture()``
takes a texture as the first argument and a ``vec2`` for the position on the
texture as the second argument. We use the ``x`` and ``z`` channels of
``VERTEX`` to determine where on the texture to look up. Note that the PlaneMesh
coordinates are within the [-1,1] range (for a size of 2), while the texture
coordinates are within [0,1], so to normalize we divide by the size of the
PlaneMesh by 2.0 and add 0.5. ``texture()`` returns a ``vec4`` of the ``r, g, b,
a`` channels at the position. Since the noise texture is grayscale, all of the
values are the same, so we can use any one of the channels as the height. In
this case we'll use the ``r``, or ``x`` channel.
Now, access the noise texture using the ``texture()`` function:
.. code-block:: glsl
@@ -198,10 +189,27 @@ this case we'll use the ``r``, or ``x`` channel.
VERTEX.y += height;
}
Note: ``xyzw`` is the same as ``rgba`` in GLSL, so instead of ``texture().x``
above, we could use ``texture().r``. See the `OpenGL documentation
<https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Vectors>`_ for more
details.
:ref:`texture() <shader_func_texture>` takes a texture as the first argument and
a ``vec2`` for the position on the texture as the second argument. We use the
``x`` and ``z`` channels of ``VERTEX`` to determine where on the texture to look
up.
Since the PlaneMesh coordinates are within the ``[-1.0, 1.0]`` range (for a size
of ``2.0``), while the texture coordinates are within ``[0.0, 1.0]``, to remap
the coordinates we divide by the size of the PlaneMesh by ``2.0`` and add
``0.5`` .
``texture()`` returns a ``vec4`` of the ``r, g, b, a`` channels at the position.
Since the noise texture is grayscale, all of the values are the same, so we can
use any one of the channels as the height. In this case we'll use the ``r``, or
``x`` channel.
.. note::
``xyzw`` is the same as ``rgba`` in GLSL, so instead of ``texture().x``
above, we could use ``texture().r``. See the `OpenGL documentation
<https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Vectors>`_ for more
details.
Using this code you can see the texture creates random looking hills.
@@ -214,7 +222,8 @@ texture, now let's learn how they work.
Uniforms
--------
Uniform variables allow you to pass data from the game into the shader. They are
:ref:`Uniform variables <doc_shading_language_uniforms>` allow you to pass data
from the game into the shader. They are
very useful for controlling shader effects. Uniforms can be almost any datatype
that can be used in the shader. To use a uniform, you declare it in your
:ref:`Shader<class_Shader>` using the keyword ``uniform``.
@@ -228,11 +237,11 @@ Let's make a uniform that changes the height of the terrain.
Godot lets you initialize a uniform with a value; here, ``height_scale`` is set
to ``0.5``. You can set uniforms from GDScript by calling the function
``set_shader_parameter()`` on the material corresponding to the shader. The value
passed from GDScript takes precedence over the value used to initialize it in
the shader.
:ref:`set_shader_parameter() <class_ShaderMaterial_method_set_shader_parameter>`
on the material corresponding to the shader. The value passed from GDScript
takes precedence over the value used to initialize it in the shader.
::
.. code-block:: gdscript
# called from the MeshInstance3D
mesh.material.set_shader_parameter("height_scale", 0.5)
@@ -245,8 +254,8 @@ the shader.
``get_surface_material()`` or ``material_override``.
Remember that the string passed into ``set_shader_parameter()`` must match the name
of the uniform variable in the :ref:`Shader<class_Shader>`. You can use the
uniform variable anywhere inside your :ref:`Shader<class_Shader>`. Here, we will
of the uniform variable in the shader. You can use the
uniform variable anywhere inside your shader. Here, we will
use it to set the height value instead of arbitrarily multiplying by ``0.5``.
.. code-block:: glsl
@@ -264,16 +273,17 @@ especially useful for animations.
Interacting with light
----------------------
First, turn wireframe off. To do so, click in the upper-left of the Viewport
again, where it says "Perspective", and select "Display Normal".
Additionally in the 3D scene toolbar, turn off preview sunlight.
First, turn wireframe off. To do so, open the **Perspective** menu in the
upper-left of the viewport again, and select **Display Normal**. Additionally in
the 3D scene toolbar, turn off preview sunlight.
.. image:: img/normal.webp
Note how the mesh color goes flat. This is because the lighting on it is flat.
Let's add a light!
First, we will add an :ref:`OmniLight3D<class_OmniLight3D>` to the scene.
First, we will add an :ref:`OmniLight3D<class_OmniLight3D>` to the scene, and
drag it up so it is above the terrain.
.. image:: img/light.webp
@@ -300,7 +310,7 @@ do that by passing in a second noise texture.
uniform sampler2D normalmap;
Set this second uniform texture to another :ref:`NoiseTexture2D <class_noisetexture2D>` with another
:ref:`FastNoiseLite <class_fastnoiselite>`. But this time, check **As Normalmap**.
:ref:`FastNoiseLite <class_fastnoiselite>`. But this time, check **As Normal Map**.
.. image:: img/normal-set.webp
@@ -312,20 +322,19 @@ wrapping the texture around the mesh automatically.
Lastly, in order to ensure that we are reading from the same places on the noise
texture and the normalmap texture, we are going to pass the ``VERTEX.xz``
position from the ``vertex()`` function to the ``fragment()`` function. We do
that with varyings.
that using a :ref:`varying <doc_shading_language_varyings>`.
Above the ``vertex()`` define a ``vec2`` called ``tex_position``. And inside the
``vertex()`` function assign ``VERTEX.xz`` to ``tex_position``.
Above the ``vertex()`` define a ``varying vec2`` called ``tex_position``. And
inside the ``vertex()`` function assign ``VERTEX.xz`` to ``tex_position``.
.. code-block:: glsl
varying vec2 tex_position;
void vertex() {
...
tex_position = VERTEX.xz / 2.0 + 0.5;
float height = texture(noise, tex_position).x;
...
VERTEX.y += height * height_scale;
}
And now we can access ``tex_position`` from the ``fragment()`` function.
@@ -345,6 +354,9 @@ We can even drag the light around and the lighting will update automatically.
.. image:: img/normalmap2.webp
Full code
---------
Here is the full code for this tutorial. You can see it is not very long as
Godot handles most of the difficult stuff for you.