Shader practice

Created by miccall (转载请注明出处 miccall.tech)

目录

  • 1.uv动画
  • 2.简单描边
  • shadertoy初探 :
    3.画一个笑脸

1. UV 动画


把一张九宫格图片。切成单附图,然后轮播出来~

Shader "miccall/uv_anim"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _SizeX ("列", Float) = 3  
        _SizeY ("行", Float) = 3
        _offsetX ("_offsetX",RANGE(0,2)) = 0 
        _offsety ("_offsetY",RANGE(0,2)) = 0 
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _SizeX ; 
            float _SizeY ;
            float _offsetX ;
            float _offsetY ;

            v2f vert (appdata v)
            {
                v2f o;
                float deltaX = 1 / _SizeX; // 单元格增量宽度
                float deltaY = 1 / _SizeY; // 单元格增量高度
                float2 cellUV = float2( v.uv.x * deltaX , v.uv.y * deltaY );

                // 当前播放总索引  speed 
                int index = _Time * 20;

                // 求列索引
                int col = fmod(index, _SizeX);
                // 求行索引
                int row = index / _SizeY ;

                cellUV.x += col * deltaX ;
                cellUV.y += row * deltaY ;

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv =   TRANSFORM_TEX(cellUV,_MainTex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }

            ENDCG
        }
    }
}

2. 简单描边

Shader "Unlit/outline"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _outlineSize("outlineSize",float ) = 0.002 
        _outlineColor("outlineColor",Color) = (1,1,1,1)
    }
    SubShader
    {
        //让渲染队列靠后,并且渲染顺序为从后向前,保证描边效果不被其他对象遮挡。  
        Tags{"Queue" = "Transparent"}  
        Pass
        {
        //先绘制这个纯色的顶点,然后在下一个pass绘制对象
        //这里不存在前后面,关闭裁剪前后面,也不需要深度缓存
        //  Cull off // 关闭剔除,模型前后都会显示
         cull Front //剔除正面,只渲染背面,对于大多数模型适用,不过如果需要背面的,就有问题了   
        //ZWrite Off // 系统默认是开的,要关闭。关闭深度缓存,后渲染的物体会根据ZTest的结果将自己渲染输出写入
        //ZTest Always  // 深度测试[一直显示],被其他物体挡住后,此pass绘制的颜色会显示出来
         ZWrite Off  
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL ;
                float2 uv : TEXCOORD0 ;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0 ;
                float4 Color : Color ;
                float4 pos : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _outlineSize ;
            fixed4 _outlineColor ;

            v2f vert (appdata v)
            {
                v2f o ;
                o.uv = v.uv ;
                o.pos  = UnityObjectToClipPos(v.vertex);
                float3 view_norm = mul( (float3x3) UNITY_MATRIX_IT_MV , v.normal ); 
                float3 Project_norm = TransformViewToProjection( view_norm ); 
                o.pos.xyz += Project_norm * _outlineSize * 0.01 ;  
                o.Color = _outlineColor ;
                return o ;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = i.Color ;
                return col;
            }
            ENDCG
        }

        Pass
        {
           ZWrite on
           CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #include "UnityCG.cginc"

           sampler2D _MainTex;
           float4 _MainTex_ST;

           struct v2f{
                float4 pos:SV_POSITION;
                float2 uv : TEXCOORD0;// 纹理,相对自身的坐标轴,float2是一个平面
           };

           v2f vert(appdata_full v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex); 
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
           }

           float4 frag(v2f i) : COLOR
           {
                float4 texCol = tex2D(_MainTex, i.uv);
                return texCol;
           }

           ENDCG
        }

    }
}

3. shadertoy初探 : 画一个笑脸

前奏 : 环境搭建
1.可以是网站直接编写预览 : shadertoy
2.使用vs-code + shadertoy 插件 github : shadertoy for vscode

我这里采用第二种编写
基本框架 :


vec4 mainImage( vec4 fragColor , vec4 fragCoord)
{
    //初始化屏幕颜色 
    fragColor = vec4(0.,0.,0.,1.0);
    //根据分辨率 计算uv 
    vec2 uv = fragCoord.xy / iResolution.xy ;
    // 用于shadertoy网站的颜色
    fragColor = vec4 ( custom_vec3, 1.0 );
    // 用于shadertoy插件的颜色  
    return fragColor;
}

void main() {
    // 因为shadertoy插件把 fragColor和fragCoord用gl_FragColor和gl_FragCoord代替。所以我们做这个框架
    gl_FragColor = mainImage( gl_FragColor , gl_FragCoord );
}

然后我们来看一下最后我们要做的结果 :

简单思路就是画圆,脸是圆,眼睛也是圆。嘴巴可以看做是两个圆相切得到的。
第一步,拿shader画一个圆 :
画圆之前,我们要知道一个函数 length() : 计算一个点,到(0,0)的距离


    float d = length(uv);
    vec3 col = vec3(d,d,d);
    fragColor = vec4(col,1.0);


可以观察到 ,左下角是黑色的 ,他的值为(0,0)向上走。y趋向1,向右走,x趋向1。
所以到了右上角,就变成(1,1)
我们试着将圆心移动到屏幕的中点 。
在计算 d 之前 , 把uv的 x ,y 都减去 0.5 ;

    uv -= 0.5 ;
    float d = length(uv);
    vec3 col = vec3(d,d,d);
    fragColor = vec4(col,1.0);


可以看到,屏幕的中心距离变成了0 ,而四周都是0.5 这里是距离,所以-0.5在length下,也变成了正的0.5
然后介绍第二个函数 smoothstep( start , end , t ); 比较 t 和限定范围 start 和 end ,如果 在start之前
(比start小,返回 0) ,比 end 后 (大) 返回 1 , 如果在 start 和 end 之间 ,返回一个过度值 (t - start )/( end - start )


    uv -= 0.5 ;
    float d = length(uv);
    vec3 col = vec3(d,d,d);
    float r = 0.3 ;
    float c = smoothstep(r, r + 0.01 , d);
    col = vec3(c,c,c);
    fragColor = vec4(col,1.0);
    return fragColor;

我们规定了一个半径 r ,让d 在 r和r+0.01 之间过度,之外二值化。看到效果这样


我们试着修改 0.01 为 0.1

    float c = smoothstep(r, r + 0.1 , d);


我们可以看到,在r之外的 0.1 范围内 。他是一个过度的着色 。
反正差不多了,先这样,但是圆怎么是椭圆的呢,我们怎么把他变成正圆?
因为屏幕不是1:1的屏幕,肯定uv映射也不一样,我们通过修改uv的x来重映射x轴

    uv.x *= iResolution.x / iResolution.y ;

这样,我们的圆就很圆了。

好了,简单总结一下,重构一下代码:
我们用distance画了一个圆,用uv重映射了坐标,修改一个值可以变模糊,我们称之为blur, 函数化一下,然后我们又想移动这个圆,好,添加一个offset ,最后,我们做一个改变,smoothstep用减去一个blur反转黑白色 。


float cricle(vec2 uv , vec2 offset , float r , float blur )
{
    float d = length(uv - offset); 
    return smoothstep(r , r - blur , d );
}

vec4 mainImage( vec4 fragColor , vec4 fragCoord)
{
    fragColor = vec4(0.,0.,0.,1.0);
    vec2 uv = fragCoord.xy / iResolution.xy ;
    uv -= 0.5 ;
    uv.x *= iResolution.x / iResolution.y ;
    float face = cricle(uv,vec2(0.,0.),0.4 ,0.03);
    vec3 col = vec3(face);
    fragColor = vec4(col,1.0);
    return fragColor;
}

我们利用这个遮罩 ,可以把光亮的颜色重新改写,然后继续添加几个圆


    float face = cricle(uv,vec2(0.,0.),0.4 ,0.03);
    float eye1 = cricle(uv,vec2(-0.17,0.1),0.07 ,0.01);
    float eye2 = cricle(uv,vec2(0.17,0.1),0.07 ,0.01);
    float mask = face - eye1 - eye2 ;
    vec3 col = mask * vec3(1.0 , 1.0 , 0.0) ;
    fragColor = vec4(col,1.0);
    return fragColor;


看样子几乎已经成型了,我们继续添加嘴巴


    float mouth = cricle(uv,vec2(0,-0.02),0.3 , 0.01);
    mouth -= cricle(uv,vec2(0,0.06),0.32,0.01);

这个原理类似 ,只不过我们让他们相间,减去一块而已
然后利用mask 再把mouth减去

     float mask = face - mouth - eye1 - eye2 ;

这样,我们的笑脸就做完了 。