[Unity Basics] one of catlike coding base articles (creating clocks, creating images)

foreword

I decided to learn the classic tutorial catlike coding of unity by myself, because catlike coding is a very good tutorial, and the things to learn are relatively comprehensive. Xiaobai can learn all aspects, and big brothers can check for omissions and make up for gaps. This is a personal study note, do not spray, welcome to point out mistakes.

Tutorial source address: https://catlikecoding.com/unity/tutorials/

1. Color space (personal notes, you can skip if you don’t understand)

1. What is a color space?

Color space refers to any value in a color model (the most commonly used in computer graphics is RGB) to calculate another value through a specific method, and there are two specific calculation methods in the color space in Unity The relationship is linear relationship and Gamma relationship. All the color spaces in Unity can choose two workflows (linear workflow and Gamma workflow).
Video explanation about Color space, Gamma, Linear and sRGB:
https://www.bilibili.com/video/BV1k64y1o7Ni/?spm_id_from=333.337.search-card.all.click&vd_source=d6b9e3a22052f82e07e715a82d495b9a

2. What are Linear, Gamma, sRGB

I、Linear

First, take light as an example. In real physics, the brightness of light is related to the number of light particles, and the linear relationship is that the intensity of light is doubled, and the brightness of light is also doubled.

II、Gamma和sRGB

Gamma was originally derived from CRT monitors. And the relationship between voltage and brightness is i = u^22, so the Gamma value of early CRT monitors is 2.2, but in modern LED monitors, Gamma is no longer needed. In order to be compatible with the previous Gamma = 2.2, modern monitors Gamma is also 2.2. sRGB is at Gamma 0.45. The purpose of sRGB is to make the brightness from 0 to 1 to make the color uniform. The following pictures are the human perception (Perceived brightness) and the physical reality (Physical brightness) in Gamma2.2. :
Please add a picture description
0.45 * 2.2 is approximately equal to 1, we have i = (u ^ 0.45) ^ 2.2, we can get i = u ^ 0.99 to achieve the Linear effect.

III, Linear workflow and Gamma workflow process

The two workflows are shown in the figure:
Please add a picture description
Linear workflow:
Linear Texture Gamma 1.0 —> Linear Gamma 1.0 —> Shader —> Gamma correction —> Gamma 0.45 —> Gamma 1.0 —> Color seen from the monitor = real world

Gamma workflow
sRBG Texture Gamma0.45 —> Shader —> Gamma 2.2 --> the color seen by the monitor

Summary:
Gamma workflow performance is good. When the texture does not need to be calculated.
Gamma workflow scope of influence: interpolation, lighting, transparency blending.

The Linear workflow has more steps and is more complicated, but the effect is good. The overhead is worth it, and it will not cause exposure problems due to Gamma correction.

Gamma workflow and Linear workflow settings in Unity
Prohect Settings > Player > other setting >Color space

Second, use Unity to realize the clock

Since this is the first time to use this Unity project, we need to change the color space of the Unity project to a Linear workflow, mainly because we want to display better color effects, as described in the first part of the introduction.

Select Edit in the toolbar in the upper left corner of Unity, click to open Project Settings, select Player, and select Linear for Color Space in Other Setting, ( Note: ) When the project is very large, converting the color space will consume a lot of time, and the lighting effects in some projects need to be readjusted].
Please add a picture description

I. Implementation steps

1. Create game objects in the scene

Create a Clock Disk

Right-click on the Hierachy panel, select Create Empty to create an empty object, and name it Clock. At this time, the object only mounts the Transform component.
insert image description here
Right-click the Clock object, and point the camera to the z-axis, and set the coordinates to (0, 0, -20).

Create a 3d object, select and create Cylinder, and change the name to Face, set XYZ of Position in Transform to (0, 0, 0), set XYZ of Rotation to (90, 0, 0), and XYZ of Scale Set to (10, 0.2, 10), the final effect is as shown below
insert image description here

Create clock ticks

We need to prepare three material balls first (creation method, right-click the mouse on the Project panel and put it on Create, select Material), respectively ClockArm, HourIndicator, SecondArm are used for the material of the hour hand, minute hand, 12 scale and second hand, as shown below:
insert image description here
The above is to create a face plate of a clock and the shader balls that need to be used. Next, create the 12 scales of the clock. Create a scale object, set the coordinates of the uppermost scale to (0,4,-0.25), drag the shader HourIndicator onto this object, and copy it to 12, as shown in the figure: you can edit the scale object through the Scene
insert image description here
panel location, this is very troublesome. Here you need to write a script to modify these scales to the correct position through the code operation.

public class ClockInit : MonoBehaviour
{
    
    
    //存放时钟十二个指示的引用
    [HideInInspector] private List<Transform> _hourIndocators = new List<Transform>();
    //x长度为4
    [HideInInspector] private float _x = 4.0f;
    //y长度为4
    [HideInInspector] private float _y = 4.0f; 
    
    private void Awake()
    {
    
     
        Transform clockTran = GameObject.Find("Clock").transform;
        for (int i = 1; i < 13; i++)
        {
    
    
            _hourIndocators.Add(clockTran.GetChild(i));
        }
        
        //计算各个指示器的位置和旋转 
        float radius = 360.0f / 12.0f;
        //刻度1 - 12
        for (int i = 0;i < _hourIndocators.Count;i++)
        {
    
    
            float sin =  Mathf.Sin(radius * i * Mathf.Deg2Rad);
            float cos =  Mathf.Cos(radius * i * Mathf.Deg2Rad);
            _hourIndocators[i].localPosition = new Vector3(_x * sin ,_y * cos , -0.25f);
            _hourIndocators[i].localRotation = Quaternion.Euler(new Vector3(0.0f,0.0f,- radius * i));
        }
    } 
}

Create this ClockInit script and hook it up to the camera. Click to run, so that the scales are all in the correct position. As shown in the figure below:
insert image description here
In fact, the principle is very simple. We have established an xy coordinate system with the center of the disc as the center, and the coordinate calculation of scale 1 is as follows:
Assuming that the coordinates of scale 1 are (x, y, -0.25), then x = x * sin(30), y = y* cos(30), so the following code:

 float sin =  Mathf.Sin(radius * i * Mathf.Deg2Rad);
 float cos =  Mathf.Cos(radius * i * Mathf.Deg2Rad);
 _hourIndocators[i].localPosition = new Vector3(_x * sin ,_y * cos , -0.25f);
 _hourIndocators[i].localRotation = Quaternion.Euler(new Vector3(0.0f,0.0f,- radius * i));

The i inside represents the first few scales. And it is necessary to modify the rotation angle of the object around the z-axis, which is exactly the scale index * -1 * 360 / scale number.

Create hour, minute, second hands

insert image description here
As shown in the figure above, create an empty sub-object for the Clock object and name it HoursArmPivot, set the coordinates to (0,0,0), and create a Cube sub-object for the object, and set the XYZ of Psotion It is (0, 0.75, -0.25), and the Scale is set to (0.3, 2.5, 0.1)
insert image description here
so that the hour hand is created. When we let the HoursArmPivot object rotate around the z-axis, the effect will be as follows:
Please add a picture description
In this way, the effect of the clock is realized, and the minute and second hands are created by repeating the above steps.
insert image description here
Since the minute hand is longer and smaller, the coordinates and scaling are set to:
insert image description here
Since the coordinates and scaling of the second hand are set to:
insert image description here
In this way, we create a clock object with 12 scales, hour hand, minute hand and second hand.

Make this clock object move

Create a Clock script and hang it on the Clock object to make the clock move. Uncomment the commented part 1, and comment out the following part 2, there are two different clock effects, you can try it. It turns out that the system time is obtained, and the angles of the hour hand, minute hand, and second hand are modified according to the running frame of the game according to the system time.

/// <summary>
/// 时钟脚本
/// </summary>
public class Clock : MonoBehaviour
{
    
    
    //引用时针、分针、秒针的pivot的transform
    [HideInInspector] private Transform _hoursPivot,_minutesPivot,_secondsPivot; 
    [HideInInspector] private const float  _hoursToDegrees = -30f, _minutesToDegrees = -6f, _secondsToDegrees = -6f;
   
    private void Start()
    {
    
    
        _hoursPivot = GameObject.Find("Clock/HoursArmPivot").transform;
        _minutesPivot = GameObject.Find("Clock/MinutesArmPivot").transform;
        _secondsPivot = GameObject.Find("Clock/SecondsArmPivot").transform;
    }

    private void Update()
    {
    
    
        //部分1 秒针抖动版时钟
        //var time = DateTime.Now; 
        //_hoursPivot.localRotation = Quaternion.Euler(new Vector3(0, 0,hoursToDegrees * time.Hour));
        //_minutesPivot.localRotation = Quaternion.Euler(new Vector3(0,0,minutesToDegrees * time.Minute));
        //_secondsPivot.localRotation = Quaternion.Euler(new Vector3(0,0,secondsToDegrees * time.Second)); 
        
        //部分2 秒针连续转动版时钟
        TimeSpan time = DateTime.Now.TimeOfDay;
        _hoursPivot.localRotation =
            Quaternion.Euler(0f, 0f, _hoursToDegrees   *(float)time.TotalHours);
        _minutesPivot.localRotation =
            Quaternion.Euler(0f, 0f, _minutesToDegrees *(float)time.TotalMinutes);
        _secondsPivot.localRotation =
            Quaternion.Euler(0f, 0f, _secondsToDegrees *(float)time.TotalSeconds);
    }
}

Part 1 Effect:
Please add a picture description
Part 2 Effect:
Please add a picture description

II. Summarize the knowledge points used

1. The scripts of game objects are all components, and game objects can be mounted at will
2. Quaternions and Euler angles
3. Trigonometric functions calculate coordinate axes
4. Programming language basics (classes, public modifiers, etc.)

3. Create an image

I. Create game objects

1. Step 1, prepare the prefabs and game objects to be used

insert image description here
Right-click on an empty space in the Hierarchy panel, select Crate Empty and change the object name to Graph.

Then create a Cube inside the 3D Object, and change the object name to Point as a prefab. Then drag and drop it into the saved prefab folder.
insert image description here

2. Step 2, create a script and use function mapping to create an image

Create a game script Graph and edit the script.

public class Graph : MonoBehaviour
{
    
    
    [SerializeField] public Transform _pointPrefabs;
    [Range(0, 100)] public float _pointNum = 10;
    
    //初始化图形代码
    void Start()
    {
    
    
        float step = 2.0f / _pointNum;
       //统一修改预制体大小
       _pointPrefabs.localScale = Vector3.one * step ;
       for (int i = 0;i < _pointNum;i++)
       {
    
    
           Transform point = Instantiate(_pointPrefabs);
           //设置创建的子对象
           point.SetParent(transform); 
       }
    }
    
    //让图像动起来
    void Update()
    {
    
    
        float time = Time.time;
        float step = 2.0f / _pointNum;
        for (int i = 0; i < _pointNum; i++)
        {
    
     
            Vector3 position = Vector3.zero;
            Transform point = transform.GetChild(i);
            position.x = (i + 0.5f) * step - 1f;
            position.y = Mathf.Sin(Mathf.PI * (position.x + time)) ;
            point.position = position;
        }
    }
}

Mount the script on the Graph object in the scene, and refer to the prefab Point in the _pointPrefabs field in the script. For a better effect, change the value of the field _pointNum to 30. As shown in the figure below:
insert image description here
Then run the code to have the following results:
Please add a picture description
The above script is nothing more than using a function idea to map the coordinates of the 3D object in space, mainly from the value of x to the value of y. The function used in the script is y = f(x) = Sin(x * PI). where x = x + time. Because time is a variable that changes over time. So the image will keep moving.

II. Add Shader effect

1. Use traditional Shader to achieve

Each pixel on the screen is processed by the rendering pipeline process, which is as follows:
vertex input -> vertex shader -> surface shader -> geometry shader -> clipping -> screen mapping -> fragment shader - >Rasterization -> Stencil Depth Test -> Screen Display

Among them, in shaderlab, high-level code editing can be performed on vertex shaders and fragment shaders. Available languages ​​are CG, HLSL, and GLSL.
Now our goal is the relationship between the x value and y value of the game object object in the previous step, if we write the rendering pipeline to achieve different color output.

First create a StandradShader, then change the name of the shader to PointShader. As shown in the figure below:
insert image description here
Clear the code created by default. (leave none). Then write the code as follows

shader "Custom/PointShader"{
    
    }

This is a semantic of shaderlab. Create a traditional pipeline with the shader "position/name" {} command. Write again. as follows:

shader "Custom/PointShader" {
    
    
   SubShader{
    
    }
   SubShader{
    
    }
   FallBack "Diffuse"
}

New statements are added here. Two SubShader{} and a FallBack "Diffuse". It can be known that when a Shader is declared using ShaderLab, there can be multiple SubShaders, and different SubShaders can write their own Pass for different graphics cards. If all SubShaders cannot match their corresponding graphics cards, then they will fall back to Unity's own standard diffuse reflection shader through FallBack "Diffuse". The name of this shader is "diffuse".

Next, write the rendering pipeline:

shader "Custom/PointShader"
{
    
    
    
    SubShader{
    
    
        Tags {
    
     "RenderType" = "Opaque"}
        LOD 200
        
        Pass{
    
    
            CGPROGRAM 
            #pragma target 3.0  //定义图形库版本 
            #pragma vertex vert //定义一个顶点着色器 
            #pragma fragment frag //定义一个片元着色器
            
            //定义一个结构体。用于输入顶点着色器
            struct adp_data
            {
    
    
                float4 vert:POSITION;  //告诉这个结构体,从模型空间获取顶点位置(语义:POSITION起的作用)
            };

            //定义一个结构。用于顶点输出,片元输入。
            struct v2f
            {
    
    
                //输出时,让顶点着色器的顶点数据放置处。输入时,片元着色器的数据获取处(语义:SV_PSOITION的作用)
                float4 vert:SV_POSITION;
                //同上。只是放置的地方和获取的地方由SV_POSITION改为TEXCOORD0
                fixed4 color:TEXCOORD0;
            };
            
            //顶点着色器的实现
            v2f vert(adp_data i)
            {
    
    
                v2f o;
                o.vert = UnityObjectToClipPos(i.vert);
                o.color = mul(unity_ObjectToWorld,i.vert);  
                return o;
            }

            //片元着色器的实现
            fixed4 frag(v2f i):SV_Target
            {
    
    
                return normalize(i.color);
            }
            
            ENDCG
        }
    }
    FallBack "Diffuse"
}

The LOD200 in Shader is set in SubShader. We can use the Shader.globalMaximumLOD global property in C# to set the lowest selection threshold of LOD. If one SubShader is set to LOD 200, the other is set to LOD 100. If you set Shader.globalMaximumLOD = 100, then the SubShader with LOD 200 will not be selected when running this Shader.

The above is a simple rendering pipeline written. The effect achieved is only to determine the color of the object's own fragment by the world coordinates of the object. If the Shader material of Point is selected as Custom/PointShader, the effect is as follows:
Please add a picture description

1. Implemented using the surf shader

Like the traditional Shader, write a structure first.

shader "Custom/PointShader" {
    
    
   SubShader{
    
    
   } 
   FallBack "Diffuse"
}

Here we can directly embed CGPROGRAM-ENDCG semantics in SubShader. There is no need to use the Pass structure. And define the surf shader. The simplicity of the surf shader is that it hides the vertex shader and the fragment shader, and there is no need to write the implementation of the shader separately. So the surf shader is relatively simple.

shader "Graph/PointSurface" {
    
     
   //定义一下面板可设置属性
   Properties{
    
    
       //设置名字和数据类型。并且赋值默认值
      _Smoothness("Smoothness" , Range(0,1)) = 0.5;
   }
   SubShader{
    
    
       Tags{
    
     "RenderType" = "Opaque"}
       LOD 200
       CGPROGRAM
       //定义一个Surface着色器。并且设置参数Standrad fullforwardshadows
       #pragma surface surf Standrad fullforwardshadows 
       #pragma target 3.0
       //定义一个输入用的结构体
       struct Input{
    
    
           float3 worldPos;
       };
       float _Smoothness;  //用于接收Properties里面的_Smoothness
       void surf(Input input,SurfaceOutputStandrad o){
    
    
           //修改反射率(光的反射率就人眼观察的颜色)
           o.Albedo = input.worldPos;
           //修改平滑度
           o.Smoothness = _Smoothness;
       }  
       ENDCG
   } 
   FallBack "Diffuse"
}

A simple surf shader is written here, which determines the color of its own element points by the world coordinates of the object. Select the material Shader of the Point prefab as Graph/PointSurface. The effect is the same as using traditional shaders. As shown in the picture:
Please add a picture description

3. Use URP (Universal Render Pipeline, Universal Render Pipeline) to achieve

Preparation

1. Create a URP file in the Project panel of the project project to store the URP
2. Select Window in the Unity menu bar. Then click to open Package Manager. Download and install the URP plugin. As shown in the picture below:
insert image description here
3. Follow the instructions after completion. Select Assets in the Unity menu bar to open in turn, Create->Rendering->Universal Render Pipeline->Pipeline Asset(forward render). The plugin generates a URP Asset file, renames it URP, and puts it in the URP folder, but it will automatically create a URP_Renderer file. As shown in the figure below:
insert image description here
4. Select the rendering pipeline for the current Unity project. Open the Unity menu bar Editor->Project Setting->Graphics. Then select the URP you just created. As shown in the figure below:
insert image description here
set up. The surf shader written above will fail, which is normal.
5. Create a file to edit Shader. Click on Assets in the Unity menu bar, and then select Create->Shader->Universal Render Pipeline->Lit Shader Graphic. Named PointURP by the way. As shown in the figure below:
insert image description here
Click PointURP twice with the mouse to open a graphic editing interface. You will find that Shaders can be edited through graphics editors without writing scripts. This is also one of the most powerful parts of the URP plug-in, as shown in the figure below:
insert image description here
Edit the above Fragment according to the needs (the fragment shader is enough).
The previous requirement was that the object pixel color was determined by the object world coordinates. After editing, the operation is as follows:
right-click the blank space and select Create Node. Search for Psoition and the plug-in comes out, link the Postion Node from the plug-in to the Base Color in the Fragment, as shown in the screenshot below:
insert image description here
Then click Save Asset in the upper left corner to save a Shader file. Then select PointURP for the material Shader of the Point prefab.
Afterwards, the same effect was achieved.
Please add a picture description

Guess you like

Origin blog.csdn.net/qq_41094072/article/details/128453919