【Unity基础】catlike coding base篇之一(创建时钟,创建图像)

前言

决定自己学习一遍unity的经典教程catlike coding,因为catlike coding是一个非常好的教程,学习的东西比较全面,小白能全方面学习,大佬能查漏补缺。这是个人学习笔记,勿喷,欢迎各位指错。

教程源地址:https://catlikecoding.com/unity/tutorials/

一、颜色空间(个人笔记,看不懂可跳过)

1、什么是颜色空间?

颜色空间是指一个色彩模型里面(计算机图形学里面最常用的是RGB)的任意值通过某种特定的方法计算出来另外一个值,而色彩空间里面某种特定的计算方法在Unity中有两种关系,分别为线性关系和Gamma关系,所有Unity中color space可以选中两种工作流(线性工作流与Gamma工作流)。
关于Color space、Gamma、Linear和sRGB有关的视频讲解:
https://www.bilibili.com/video/BV1k64y1o7Ni/?spm_id_from=333.337.search-card.all.click&vd_source=d6b9e3a22052f82e07e715a82d495b9a

2、什么是Linear、Gamma、sRGB

I、Linear

首先拿光照为例子,在现实物理里,光的亮度和光的粒子数量有关,而线性关系是光的强度增强一倍,那么光的亮度就跟着增强一倍。

II、Gamma和sRGB

Gamma最早来源于CRT显示器。且电压和亮度之间的关系为i = u^22 所以早期的CRT显示器的Gamma值均为2.2,而在现代的LED显示器其实已经不需要Gamma了,为了兼容以前的Gamma = 2.2,现代显示器的Gamma也是2.2。sRGB则是处于Gamma 0.45,sRGB的目的是为了使亮度从0到1之间使得颜色变得均匀,如下图分别是人眼感知(Perceived brightness)和在Gamma2.2中的物理真实(Physical brightness):
请添加图片描述
0.45 * 2.2 约等于1,我们就有i = (u ^ 0.45) ^ 2.2,就能得到 i = u ^ 0.99 从而实现出Linear的效果。

III、Linear工作流和Gamma工作流的流程

两工作流如图所示:
请添加图片描述
Linear工作流:
Linear Texture Gamma 1.0 —> Linear Gamma 1.0 —> Shader —> Gamma矫正 —> Gamma 0.45 —> Gamma 1.0 —> 从显示器看到的颜色 = 现实世界

Gamma工作流
sRBG Texture Gamma0.45 —> Shader —> Gamma 2.2 -->显示器看到的颜色

总结:
Gamma工作流性能好。贴图不用进行关照计算的时候。
Gamma工作流的影响范围:插值、光照、透明混合。

Linear工作流相对步骤多而复杂,但是效果好,这个开销是值得的,不会因为Gamma矫正而引起曝光问题。

Unity中Gamma工作流和Linear工作流的设置
Prohect Settings > Player > other setting >Color space

二、使用Unity实现时钟

由于此处是第一次使用到这个Unity工程,我们需要把Unity工程的色彩空间改成Linear工作流,主要是想要显示的色彩效果更好,关于介绍上篇有。

选择Unity的左上角的工具栏里面的Edit,点击打开Project Settings选中Player,里面的Other Setting的Color Space选择Linear,(注意:)【这里一般是要在项目开始的时候决定使用哪个色彩空间,当项目工程非常庞大的时候,转换色彩空间会消耗非常多的时间,并且一些项目里的光照效果需要重新调整】。
请添加图片描述

I、实现步骤

1、在场景中创建游戏对象

创建时钟圆盘

在Hierachy面板上右击,选择Create Empty创建一个空对象,并且命名为Clock,此时这个物体只挂载着Transform组件。
在这里插入图片描述
鼠标右击Clock对象,并且把摄像机对准z轴,设置坐标为(0,0,-20)。

创建一个3d物体对象,选中并且创建Cylinder,并且把名字改为Face,Transform中的Position的XYZ设置为(0,0,0)、Rotation的XYZ设置为(90,0,0),Scale的XYZ设置为(10,0.2,10),最终效果如下图
在这里插入图片描述

创建时钟刻度

我们需要先准备三个材质球(创建方式,在Project面板下右击鼠标放到Create上面,选中Materal),分别为ClockArm、HourIndicator、SecondArm用于时针分针、12刻度以及秒针的材质,如下图:
在这里插入图片描述
以上是创建一个时钟的脸盘和需要用到的材质球,下面对时钟的12个刻度进行创建。创建刻度对象,位置最上面的刻度坐标设定为(0,4,-0.25),把材质球HourIndicator拖拽上这份对象,并且复制为12个,如图:
在这里插入图片描述
可以把刻度对象通过Scene面板编辑位置,这样很麻烦。这里需要写一个脚本通过代码运行把这些刻度修改到正确的位置上。

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));
        }
    } 
}

创建这个ClockInit脚本并且挂到摄像机上面。点击运行,这样刻度就都在正确的位置上面了。如下图:
在这里插入图片描述
其实原理很简单,我们已这个圆盘的中间为圆心建立xy坐标系,就有如下刻度1的坐标计算:
假设刻度1的坐标为(x,y,-0.25),则x = x * sin(30),y = y* cos(30),所以则有如下代码:

 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));

里面的i代表了第几个刻度。并且需要修改改物体围绕z轴的旋转角度,刚刚好是刻度索引 * -1 * 360 / 刻度数量。

创建时针、分针、秒针

在这里插入图片描述
如上图所示,为Clock对象创建一个空的子物体并且命名为HoursArmPivot,设置坐标为(0,0,0),而为该物体再创建出来的一个Cube的子物体,并且把Psotion的XYZ设置为(0,0.75,-0.25),Scale设置为(0.3,2.5,0.1)
在这里插入图片描述
这样就把时针创建出来了。当我们让HoursArmPivot对象围绕z轴旋转时候,会如下效果:
请添加图片描述
这样就实现了,时钟的效果,重复以上步骤创建出来分针和秒针。
在这里插入图片描述
由于分针更长跟细小,所以坐标和缩放设置为:
在这里插入图片描述
由于秒针的坐标和缩放设置为:
在这里插入图片描述
这样我们就创建出来了一个有12刻度、时针、分针和秒针的一个时钟对象。

让这个时钟对象动起来

创建一下的Clock脚本,把他挂在到Clock对象上就能让时钟动起来。把注释的部分1取消注释,把下面的部分2注释掉,是两种不同的时钟效果,可以尝试一下。原来就是获取系统时间,根据系统时间按照游戏的运行帧来修改时针、分针、秒针的角度。

/// <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);
    }
}

部分1效果:
请添加图片描述
部分2效果:
请添加图片描述

II、总结使用到的知识点

1.游戏对象的脚本皆为组件,可以随意挂载游戏对象
2.四元数与欧拉角
3.三角函数计算坐标轴
4.编程语言基础(类、公共修饰符等)

三、创建一个图像

I、创建游戏对象

1、步骤一,准备需要使用的预制体和游戏对象

在这里插入图片描述
Hierarchy面板空白处鼠标右击,选择Crate Empty并且把对象名字修改为Graph。

再创建一个3D Object里面的Cube,并且把对象名字修改为Point用作预制体。之后拖拽到保存的预制体文件夹里面去。
在这里插入图片描述

2、步骤二,创建脚本,利用函数映射创建图像

创建一个游戏脚本Graph,并且对脚本进行编辑。

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;
        }
    }
}

把脚本挂载到场景内的Graph对象上面,并且把脚本里面的_pointPrefabs字段引用预制体Point,为了效果好看一点,把字段_pointNum的值修改成30。如下图:
在这里插入图片描述
然后运行代码有如下结果:
请添加图片描述
上面的脚本无非就是,使用一种函数的思想去映射3d物体在空间中的坐标,主要是由x的值映射到y的值。脚本里使用的函数为y = f(x) = Sin(x * PI)。其中x = x + time。因为time是一个会随着时间而改变的变量。所以这个图像会一直动起来。

II.添加Shader效果

1.使用传统的Shader实现

屏幕上的每一个像素点经过渲染管线的流程处理的,这个流程如下:
顶点输入->顶点着色器->曲面着色器->几何着色器->裁剪->屏幕映射->片元着色器->光栅化处理->模板深度测试->屏幕显示

其中在shaderlab里面对可以对顶点着色器和片元着色器进行高度代码编辑。可使用语言有CG、HLSL、GLSL。
现在我们的目的是上一个步骤的游戏物体对象的x值和y值的关系,如果编写渲染管线来实现不同颜色的输出。

先创建一个StandradShader,再把shader的名字改成PointShader。如下图:
在这里插入图片描述
清空默认创建的代码。(全部不留)。然后写入代码如下

shader "Custom/PointShader"{}

这是shaderlab的一个语义。通过shader “位置/名字” {} 命令来创建一个传统流水线。再编写。如下:

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

这里添加了新的语句。两个SubShader{}以及一句FallBack “Diffuse”。可以知道,一个使用ShaderLab声明一个Shader的时候,可以有多个SubShader,而不同的SubShader可以针对不同的显卡去编写自己的Pass。如果所有SubShader都无法匹配到自己对应的显卡,那么就会通过FallBack “Diffuse"回退到Unity自己的自带的标准漫反射shader,这个shader名字是"diffuse”。

接下来,再编写渲染管线:

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"
}

Shader里面的LOD200是设置在SubShader里面的,我们可以利用C#里面的Shader.globalMaximumLOD全局属性里设定LOD最低的选择阈值。假如一个SubShader里面设定LOD 200,另一个设定LOD 100。假如设定Shader.globalMaximumLOD = 100,那么在运行这个Shader的时候不会选择LOD 200的SubShader。

上面是一个编写了一个简单的渲染管线。实现的效果只是由物体的世界坐标去决定物体自身片元的颜色。把Point的Shader材质选择为Custom/PointShader则效果如下:
请添加图片描述

1.使用surf着色器实现

和传统Shader一样,先编写一个结构。

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

这里我们可以直接在SubShader里面嵌入CGPROGRAM-ENDCG语义了。不需要使用Pass结构。并且定义surf着色器。surf着色器简单的地方就是把顶点着色器和片元着色器隐藏了,无需各自编写着色器的实现。所以surf着色器相对比较简单。

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"
}

这里编写了一个简单的surf着色器,由物体的世界坐标去决定自身的元素点颜色。把Point预制体的材质Shader选择为Graph/PointSurface。效果和使用传统着色器一样。如图:
请添加图片描述

3.使用URP(Universal Render Pipeline,通用渲染管线)实现

前置准备

1.项目工程的Project面板创建一个URP文件来存放URP
2.选择Unity菜单栏里的Window。之后点击打开Package Manager。下载并且按照URP插件。如下图:
在这里插入图片描述
3.按照完成之后。选择Unity菜单栏的Assets依次打开,Create->Rendering->Universal Render Pipeline->Pipeline Asset(forward render)。插件出一个URP的Asset文件,改名为URP,并且放置到URP文件夹里面,不过会自动创建一个URP_Renderer文件。如下图:
在这里插入图片描述
4.给Unity当前项目选择渲染管线。打开Unity菜单栏Editor->Project Setting->Graphics。然后选择刚刚创建的URP。如下图:
在这里插入图片描述
设置好。上面编写的surf着色器会失效,这是正常的现象。
5.创建一个编辑Shader的文件。点击Unity菜单栏Assets,然后依次选择Create->Shader->Universal Render Pipeline->Lit Shader Graphic。顺便命名为PointURP。如下图:
在这里插入图片描述
鼠标点击两下PointURP,打开一个图形编辑界面。你会发现可以通过图形编辑来编辑Shader,无需编写脚本。这也是URP插件非常牛逼的地方之一,如下图:
在这里插入图片描述
根据需求编辑上面的Fragment(片元着色器就可以了)。
之前的需求是,由物体世界坐标决定物体像素颜色。编辑后操作如下:
空白处右击鼠标,选择Create Node。搜索Psoition并且插件出来,把插件出来的Postion Node链接到Fragment里面的Base Color,如下截图:
在这里插入图片描述
然后就左上角点击Save Asset,保存下来一个Shader文件。然后给Point预制体的材质Shader选择为PointURP。
之后就实现出来一模一样的效果了。
请添加图片描述

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值