Содержание
Shadertoy is created by Beautypi (Inigo Quilez and Pol Jeremias).
Very special thanks to Reinder Nijhoff, Patrick Labatut, Henrique Lorenzi, Otavio Good, Philip Wagner, Yanling He, Juan A. Martinez (stage7), Mari Miyashita, Nikochan, Sara Goepfert, Jose Manuel Perez (JosSs), Teresa, Sara (Gizma), Brett (AudEo Flow), Dave Hoskins, Osama Mahmood, Joan Perez, Kamran Saifullah, Chintu Solanki, Eduardo (@deb_security).
We’d also like to thank all these wonderful people for their support via Patreon:
Смысл Shadertoy довольно прост: пользователи могут писать шейдеры на языке GLSL и тут же видеть, как будет выглядеть результат работы программы. А затем выставить свое творение на суд общественности, чтобы собрать лайки и комментарии. Лучшие работы попадают в топы.
Смотреть их, кстати, одно удовольствие. Чего только не делают при помощи шейдеров! Рисуют примитивы и даже целые сцены, генерируют приятные спецэффекты и завораживающие ландшафты. Кто-то даже написал подобие игры Wolfenstein 3D — всего на каких-то 440 строк.
Если захочешь разобраться, как все это работает, то можешь изучать и пробовать менять чужой код прямо на месте. Ну а более фундаментальные знания можешь почерпнуть, например, на сайте WebGLFundamentals, там есть неплохая статья о шейдерах и GLSL.
Shadertoy.com is a site that gathers user contributed GL shaders. It is a great resource for finding shader code and inspiration. In this tutorial we will take a shader from Shadertoy and make it run in Defold. Some basic understanding of shaders is assumed. If you need to read up, the Shader manual is a good place to start.
The shader we will use is Star Nest by Pablo Andrioli (user “Kali” on Shadertoy). It is a purely procedural mathematical black magickery fragment shader that renders a really cool starfield effect.
The shader is just 65 lines of quite complicated GLSL code, but don’t worry. We’re gonna treat it as a black box that does its thing based on a few simple inputs. Our job here is to modify the shader so it interfaces with Defold instead of Shadertoy.
Something to texture
The Star Nest shader is a pure fragment shader, so we only need something for the shader to texture. There are a number of options: a sprite, a tilemap, a GUI or a model. For this tutorial we are going to use a simple 3D model. The reason is that we can easily make the model rendering into a full screen effect—something we need to do if we want to do visual post processing, for example.
We start by creating a quadratic plane mesh in Blender (or any other 3D modelling program). For convenience the 4 vertex coordinates are at -1 and 1 on the X-axis and -1 and 1 on the Y axis. Blender has the Z-axis up by default so you need to rotate the mesh 90° around the X-axis. You should also make sure that you generate correct UV-coordinates for the mesh. In Blender, enter Edit Mode with the mesh selected, then select Mesh ▸ UV unwrap. ▸ Unwrap .
Blender is a free, open-source 3D software which can be downloaded from blender.org.
- Export the model as a Collada file called quad.dae and drag it into a new Defold project.
- Open your “main.collection” file in Defold and create a new game object “star-nest”.
- Add a Model component to “star-nest”.
- Set the Mesh property to the quad.dae file.
- The model is small (2⨉2 units) so we need to scale the “star-nest” game object to a reasonable size. 600⨉600 is a nice large size so set the X and Y scale to 300.
The model should appear in the scene editor, but it is rendered all black. That is because it has no material set yet:
Creating the material
Create a new material file star-nest.material, a vertex shader program star-nest.vp and a fragment shader program star-nest.fp:
- Open star-nest.material.
- Set the Vertex Program to star-nest.vp .
- Set the Fragment Program to star-nest.fp .
- Add a Vertex Constant and name it “view_proj” (for “view projection”).
- Set its Type to CONSTANT_TYPE_VIEWPROJ .
Add a tag “tile” to the Tags. This is so that the quad is included in the render pass when sprites and tiles are drawn.
Open the vertex shader program file star-nest.vp. It should contain the following code. Leave the code as is.
Open the fragment shader program file star-nest.fp and modify the code so the fragment color is set based on the X and Y coordinates of the UV coordinates ( var_texcoord0 ). We do this to make sure we have the model set up correctly:
Now the editor should render the model with the new shader and we can clearly see if the UV coordinates are correct; the bottom left corner should have black color (0, 0, 0), the top left corner green color (0, 1, 0), the top right corner yellow color (1, 1, 0) and the bottom right corner should have red color (1, 0, 0):
The star nest shader
Now everything is in place to start working on the actual shader code. Let’s first take a look at the original code. It consists of a few sections:
Lines 5–18 defines a bunch of constants. We can leve these as is.
Lines 21 and 63 contains the input fragment X and Y screen space texture coordinates ( in vec2 fragCoord ), and output fragment color ( out vec4 fragColor ).
In Defold, the input texture coordinates are passed from the vertex shader as UV coordinates (in the range 0–1) through a varying variable var_texcoord0 . The output fragment color is set to the built in variable gl_FragColor .
Lines 23–27 sets up the dimensions of the texture as well as movement direction and scaled time. The resolution of the viewport/texture is passed to the shader as uniform vec3 iResolution . The shader calculates UV style coordinates with the right aspect ratio from the fragment coordinates and the resolution. Some resolution offsetting is also done to get a nicer framing.
The Defold version needs to alter these calculations to use the UV coordinates from var_texcoord0 .
Time is also set up here. It is passed to the shader as uniform float iGlobalTime . Defold does not currently support float uniforms so we need to prov >vec4 instead.
Lines 29–39 sets up the rotation of the volumetric rendering, with mouse position affecting the rotation. The mouse coordinates are passed to the shader as uniform vec4 iMouse .
For this tutorial we are going to skip mouse input.
Lines 41–62 is the core of the shader. We can leave this code as is.
The modified star nest shader
Going through the sections above and doing the necessary changes results in the following shader code. It has been cleaned up a little for better readability. The differences between the Defold and Shadertoy versions are noted:
- The vertex shader sets a varying var_texcoord0 with the UV coordinates. We need to declare it.
- Shadertoy has a void mainImage(out vec4 fragColor, in vec2 fragCoord) entry point. In Defold we get no parameters to main() . So instead, we read the varying var_texcoord0 and write to gl_FragColor .
- For this tutorial we define static resolution for the rendering. Currently the model is square so we can use vec2 = vec2(1.0, 1.0); . With a rectangular model of size 1280⨉720 we instead set vec2 res = vec2(1.78, 1.0); and multiply the uv coordinates with that to get the correct aspect ratio.
- For the time being, time is set to zero. We will add time in the next stage.
- We’ll keep this tutorial simple by removing the iMouse values altogether. Note that we still use the rotation calculations to reduce visual symmetry in the volumetric rendering.
- Finally, set the resulting fragment color.
Save the fragment shader program. The model should now be nicely textured with a star field in the Scene editor:
Animation
The final piece of the puzzle is the introduction of time to make the stars move. To pass a time value to the shader we need to use a shader constant, a uniform. To set up a new constant:
- Open star-nest.material.
- Add a Fragment Constant and name it “time”.
- Set its Type to CONSTANT_TYPE_USER . Leave the x, y, z and w components at 0.
Now we need to modify the shader code to declare and use the new constant:
- Declare a new uniform of type vec4 with name “time”. It should suffice to keep it at lowp (Low precision).
- Read the x component of the time uniform and use it to calculate a time value.
The final step is to feed a time value to the shader:
- Create a new script file star-nest.script.
- Enter the following code:
- Store a value t in the script component ( self ) and intialize to 0.
- Each frame increase the value of self.t with the number of seconds that has passed since the last frame. This value is available through the parameter dt (delta time) and is 1/60 ( update() is called 60 times a second).
- Set the “time” constant on the model component. The constant is a vector4 so we use the x component for the time value.
Finally, add star-nest.script as a script component to the “star-nest” game object:
And that’s it! We are done!
You can view the results here:
A fun continuation exercise is to add the original mouse movement input to the shader. It should be fairly straightforward if you grasp how to deal with input.