me10001 发表于 2020-11-25 09:16

[catlikecoding]:新坑,unity编程教程学习笔记

完全做翻译是不现实的了。但是终归还是应该做一下学习笔记,比如这个tutorial 2,我颠来倒去反复做了很多次,虽然每次都觉得学到了一些,但是到了自己写的时候还是够呛。
原文在这里:
catlike coding的教程有一个好处,就是不会一次把所有的代码甩你脸上,而是一步一步的,中间还有不少反复修改的地方。(是的,中间有一些代码是故意写错的)所以一篇教程读下来就好像有个人在你面前live coding一样。这样子读起来确实不错,但是回头想要快速学习的时候就没有那么友好了。所以这群笔记算作一个补充,给自己也给有类似需要的人希望能有些帮助。
说句题外话,我以前主要的瞎编语言是javascript,虽然我也知道npm让人有些头痛,但是总是没法下决心转到python来。开始翻译automate the boring stuff with python 以后,再遇到有不懂的地方直接去自己专栏看就可以了。很多时候都能找到满意的答案。所以写专栏还是有些帮助的。成型后的graph类代码:
using UnityEngine;
public class Graph: MonoBehaviour{
        public Transform pointPrefab;
        Transform[] points;
        public int resolution = 10;
        void Awake(){
                float step = 2f/resolution;
                Vector3 scale = Vector3.one * step;
                Vector3 position;
                points = new Transform;
                position.y = 0;
                position.z = 0;
                for(int i = 0;i<resolution;i++){
                        Transform point = Instantiate(pointPrefab);
                        position.x = (i+0.5f)*step-1f;
                        point.localPosition = position;
                        point.localScale = scale;
                        point.SetParent(transform,false);
                        points = point;
                }
        }
        void Update(){
                for (int i=0;i<points.Length;i++){
                        Transform point = points;
                        Vector3 position = point.localPosition;
                        position.y = Mathf.Sin(Mathf.PI * (position.x+Time.time));
                        point.localPosition = position;
                }
        }
}shader部分的代码先不放了,暂时讲不清。
上面graph类代码掰开来讲有这么几块:
生成一条线

用Instantiate(transform)生成若干个点,把这些点排成一排就成了线:
一般来说一个点(用cube表示)单位尺寸是1x1x1,所以10个cube排在一起长度就是10,在Awake里面生成这样的一条线,代码如下:
void Awake(){
    for(int i = 0;i<10;i++){
      Transform point = Instantiate(pointPrefab);
      // set position for each point according to i:
      point.localPosition = Vector3.right * i;
    }
}现在如果要把直线的尺寸从10变成1,每个cube要缩小到0.1,同时中心位置也要缩小成下面这样:
>> 但是中心位置在0的话,cube的左半边会跑到y轴左边,所以要向右偏倚0.5,这个偏倚的量同样也要缩小,对应关系如下:
>> 计算表达式如下:
newX = (oriX+0.5)*shrinkRatio根据需求,现在从10缩小到2的长度,然后左偏倚1成为-1至1的范围:
newX = (oriX+0.5)/5 - 1所以for循环内部变成:
for(int i = 0; i<10;i++){
    Transform point = Instantiate(pointPrefab);
    point.localPosition = Vector3.right * ((i+0.5f)/5 -1f );
    point.localScale = Vector3.one / 5f;
}将参数做进上面的过程里

在Graph类下面添加一个变量
public int resolution = 10;这个10 就是现在所取的分辨率,而我们需要将这么多的cube挤到-1到1这个长度为2的区间里,所以cube之间的间距(步长)就是:
float step = 2f/resolution;算起来在resolution是10的时候,step就是5,所以不仅适用于中心间距,也适用于scale。把两个vector3提到循环以外,程序变成下面这样:
      float step = 2f / resolution;
      // 这样直接就可以把sclVect后面的/resolution * 2变成step了
      Vector3 posVect;
      posVect.z = 0;
      Vector3 sclVect =Vector3.one * step
      for (int i = 0; i< resolution; i++){
            Transform point = Instantiate(pointPrefab);
            posVect.x = (i+0.5f) * step - 1f;
            posVect.y = posVect.x;
            //设置x和y之间的关系:y = x
            point.localPosition = posVect;
            point.localScale = sclVect;
      } 顺便设置一下各个prefab的parent:
point.setParent(transform,false);设置Shader

Shader程序是GPU用来渲染三维物体的。Unity的材质可以选取Shader,通过Shader设置材质的各种属性。除了使用Unity自带的,还可以自建一个shader。Shader的建立:Assets/Create/Shader/Stander Surface Shader,命名为ColoredPoint。
打开ColoredPoint可以看到程序如下所示:
Shader "Custom/ColoredPoint" {
        Properties {
                _Color ("Color", Color) = (1,1,1,1)
                _MainTex ("Albedo (RGB)", 2D) = "white" {}
                _Glossiness ("Smoothness", Range(0,1)) = 0.5
                _Metallic ("Metallic", Range(0,1)) = 0.0
        }
        SubShader {
                Tags { "RenderType"="Opaque" }
                LOD 200
               
                CGPROGRAM
                #pragma surface surf Standard fullforwardshadows

                #pragma target 3.0

                sampler2D _MainTex;

                struct Input {
                        float2 uv_MainTex;
                };

                half _Glossiness;
                half _Metallic;
                fixed4 _Color;

                UNITY_INSTANCING_CBUFFER_START(Props)
                UNITY_INSTANCING_CBUFFER_END

                void surf (Input IN, inout SurfaceOutputStandard o) {
                        fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
                        o.Albedo = c.rgb;
                        o.Metallic = _Metallic;
                        o.Smoothness = _Glossiness;
                        o.Alpha = c.a;
                }
                ENDCG
        }
        FallBack "Diffuse"
}目前只修改Albedo相关的内容:
Shader "Custom/ColoredPoint" {
    Properties {
      _Glossiness("Smoothness",Range(0,1)) = 0.5
      _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      LOD 200

                CGPROGRAM
                #pragma surface surf Standard fullforwardshadows

      struct Input {

      };

                half _Glossiness;
                half _Metallic;

      UNITY_INSTANCING_CBUFFER_START(Props)
      UNITY_INSTANCING_CBUFFER_END

      void surf (Input IN, inout SurfaceOutputStandard o) {
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
      }
      ENDCG
    }
    FallBack "Diffuse"
}目前shader还和Unity里面的东西没有任何关联,因为我们还没修改过Input,修改Input如下:
Struct Input {
    float3 worldPos;
}然后让surf里的o属性跟Input关联起来:
o.Albedo.r = IN.worldPos.x;x的值从-1到1,但是实际上rgb是不能取负值的,所以左半边会一直保持黑色。作一下变换,让-1~1的值变成0~1的范围:
o.Albedo.r = IN.worldPos.x * 0.5 + 0.5;另外顺便把y也用进来设置g值:
o.Albedo.rg = IN.worldPos.xy * 0.5 + 0.5;Shader的写法还真是奇怪哦,可以这么把一个向量的两个分量放在一行写。
让线动起来

目前在Awake的时候设置了一下函数形态以后,接下来就没有然后了。如果这个函数能动起来那不是很好么?实际上让函数随时间变化,就是在x变量的基础再加一个t变量:f(x,t)。不过如果除了Awake的时候Instantiate一下,后面还想设置这些点的话,首先要用个数组跟踪他们。
points = new Transform;
for (int i=0;i<resolution;i++){
...
points = point因为现在y每帧都变,所以Awake就没必要设置y了,Awake部分的代码如下:
        void Awake(){
                float step = 2f/resolution;
                Vector3 scale = Vector3.one * step;
                Vector3 position;
                points = new Transform;
                position.y = 0;
                position.z = 0;
                for(int i = 0;i<resolution;i++){
                        Transform point = Instantiate(pointPrefab);
                        position.x = (i+0.5f)*step-1f;
                        point.localPosition = position;
                        point.localScale = scale;
                        point.SetParent(transform,false);
                        points = point;
                }
        }接下来是Update:
        void Update () {
                for (int i = 0; i < resolution; i++) {}
        }除了用resolution,其实还可以用points.Length。接下来每次更新,都遍历points列表,设置point的y值:
                for (int i = 0; i < points.Length; i++) {
                        Transform point = points;
                        Vector3 position = point.localPosition;
                        position.y = Mathf.Sin(position.x);
                        point.localPosition = position;
                }之所以先把point.localPosition交给position,设置好以后又重新交给point是因为localPostion作为一个属性,只接受Vector3值。上面用Sine函数设置y,但是还是没看到时间的影子。另外如果像上面这样写,Sine函数参数取值范围只有-1到1,看起来一点也不像正弦函数。所以还需要给参数乘一个PI:
position.y = Mathf.Sin(Mathf.PI * position.x);上面说到现在还是没和时间扯上关系,让函数的形式变成:y = Sin( https://www.zhihu.com/equation?tex=%5Cpi * (x+t))就可以了。而t这个变量可以用Time.time获得。程序如下:
position.y = Mathf.Sin(Mathf.PI * (position.x + Time.time));
页: [1]
查看完整版本: [catlikecoding]:新坑,unity编程教程学习笔记