Shader practice

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

目录

  • 1.uv动画
  • 2.简单描边
  • shadertoy初探 :
    3.画一个笑脸
  • 4.画一个矩形
  • 5.完善一个新的笑脸

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 ; 

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

4.画一个矩形

继续使用shadertoy的平台,画一个矩形,我们分两种来画,一种是遮罩加,一种遮罩减。得到的效果就是,一个是白色的矩形,一个是黑色的矩形。
这里就不细讲了,直接代码

// 画一个黑色的矩形 
float RectB(vec2 uv , vec2 offset , float top ,float bottom ,float left ,float right ,float blur ,float size ){

    uv -= offset ;
    uv /= size ;
    float mask = smoothstep(uv.x + blur , uv.x - blur , left);
    mask += smoothstep(uv.x - blur , uv.x + blur , -right);
    mask += smoothstep(uv.y + blur , uv.y - blur ,top );
    mask += smoothstep(uv.y - blur , uv.y + blur ,-bottom );
    return mask ; 
}

第二个是一个白色的矩形 :


float blend (float t , float start ,float end ,float blur )
{
    float step1 = smoothstep(start+blur ,start-blur ,t );
    float step2 = smoothstep(end - blur ,end + blur ,t );
    return step1*step2 ;
}

float RectW(vec2 uv ,vec2 offset , float top ,float bottom ,float left ,float right ,float blur ,float size)
{
    uv -= offset ;
    uv /= size ;
    float mask = blend(uv.x , right ,-left ,blur);
    mask *= blend(uv.y ,top,-bottom , blur);
    return mask ;
}

基本思路也跟画圆是一样的,拿smooth不断挤出和裁剪。
那么我们试着堆积两种效果,做一个环出来 :

    // 画一个黑色的rect
    float mask = RectB(uv,vec2(0.0,0.0),0.9,0.9,0.9,0.9,0.03,0.3);
    // 加画一个白色的rect
    mask += RectW(uv,vec2(0.,0.0),0.9,0.9,0.9,0.9,0.03,0.2);

    vec3 col = mask * vec3(1.0 , 1.0 , 1.0) ;
    fragColor = vec4 (col , 1.0 ); 
    return fragColor;

我们可以看到,白色的矩形大小为 0.2 倍 , 黑的是0.3倍 ,所以结果可想而知是一个黑色的边框 。

其他矩形参数调整 :
斜切 :

    float x = uv.x  ;
    float y = uv.y  ;
    float top = 0.9 ,bottom= 0.9 ,left= 0.9 ,right= 0.9 ;
    x += y;
    float mask = RectW(vec2(x,y),vec2(0.0,0.0),top,bottom,left,right,0.03,0.2); 

    float x = uv.x  ;
    float y = uv.y  ;
    float top = 0.9 ,bottom= 0.9 ,left= 0.9 ,right= 0.9 ;
    top += x * 2.0 ;
    right += y * 3.0;
    float mask = RectW(vec2(x,y),vec2(0.0,0.0),top,bottom,left,right,0.03,0.2); 

画一个函数图像 :
y = (x+0.5)(x-0.5) 是一个抛物线

    y -= ( x - 0.5) * ( x + 0.5 ) ;
    float mask = RectW(vec2(x,y),vec2(0.0,0.0),top/2.0,bottom/2.0,left*2.0,right*2.0,0.03,0.2);

这样,矩形的变化就先说到这里。下面介绍一些简单的算法思想:

顶点重映射

//把 t 重映射到 a-b 之间  
float remap(float a, float b,float t)
{
    return clamp( (t-a) / ( b-a) , 0.0 , 1.0 );
}

这个函数的意思就是 当 t 等于最小值 a 时 ,就返回0 , 当等于最大值 b 时 , 就返回1 。如果在 a,b之间呢。就是他们的所占比值了。然后我们用了一个clamp函数。他的作用就是把刚刚我们重映射的值,限制在 第二个参数和第三个参数之间,也就是这里的0到1之间。如果比1大,就返回1,比0小,就返回0,看似和我们写的remap一样,其实不然,如果在0到1之间呢,他是不会做改变的,原样返回。

重载的顶点重映射函数

float remap(float a, float b,float c , float d ,float t)
{
    return clamp( ((t-a) / ( b-a)) * (d - c) + c  , 0.0 , 1.0 );
} 

刚刚是三个参数的重映射,我们把他拓展到五个参数的重映射 。

限定范围坐标

vec2 within (vec2 uv , vec4 rect )
{
    return (uv - rect.xy ) / ( rect.zw - rect.xy ) ; 
}

把 uv 值 限定在 一个rect的矩形中。

5.完善一个新的笑脸

vec4 cricle(vec2 uv,float r , float blur)
{
    vec4 col = vec4 ( 0.9 ,  0.65 , 0.1 , 1.0 ); 
    float d = length (uv);
    col.a *= smoothstep(r, r-blur , d) ;
    float edag = remap(r - 0.15  , r , d ) ; 
    edag *= edag ;
    col.rgb *= 1.0 - edag * 0.5 ;
    // blend outline 
    col.rgb = mix (col.rgb , vec3(0.6,0.3,0.1),smoothstep(r-blur*3.,r,d));
    // blend hight light ; 
    float hightsize = 0.08 ;
    float hightlight = smoothstep(r-hightsize ,r-hightsize-blur , d );
    hightlight *= remap(r-hightsize , -0.05 , r + hightsize*5.0  , 0.1 , uv.y );
    col.rgb = mix (col.rgb , vec3(1.),hightlight);

    vec2 cheekpos = vec2(0.215,-0.18);
    float cheekdistance = length(uv - cheekpos);
    float cheeksize = 0.17 ; 
    float cheekblur = blur * 15.2 ;
    vec3 cheekcolor = vec3(1.0 , 0.0 , 0.0 ) ;
    float cheek = smoothstep(cheeksize , cheeksize - cheekblur , cheekdistance) * 0.4 ;
    cheek *= smoothstep(0.17,0.16 , cheekdistance);
    col.rgb = mix(col.rgb , cheekcolor , cheek );
    return col ;
}
vec4 Mouth(vec2 uv,float r , float blur)
{
    uv -= 0.5 ;
    uv.y *= 1.5  ; 
    uv.y -= uv.x * uv.x * 3.6 ;
    float d = length (uv); 
    vec4 col = vec4(0.5,0.18,0.05,1.0);
    col.a = smoothstep(r , r - blur , d );

    float td = length (uv - vec2(0.,  0.4 ));
    vec3 toothcol = vec3(1.0 ,1.0 ,1.0 );
    toothcol *= smoothstep(0.5 , 0.29  ,d); 
    col.rgb = mix (col.rgb , toothcol  , smoothstep(0.3 , 0.3 - blur , td ));
    return col;
}
vec4 eyecricle(vec2 uv,float r , float blur)
{
    uv -= 0.5 ;
    float d = length (uv);
    vec4 iriscol = vec4(0.3,0.5,1.0,1.0);
    vec4 col = mix(vec4 ( 1.0 ,  1.0 , 1.0 , 1.0 ),iriscol , smoothstep(0.1,0.4,d)*0.5);
    float uvy = max(0.0 , -uv.y - uv.x ); 
    col.rgb *= 1.0 - smoothstep(r - blur * 2.0 , r   , d ) * 0.6 * uvy;
    float eyespheresize = r - 0.12 ; 
    col.rgb = mix(col.rgb , vec3(0.0),smoothstep(eyespheresize , eyespheresize  - blur ,d)); 
    float eyesphereoutlinesize = eyespheresize - 0.017 ; 
    iriscol.rgb *= 1.0 + smoothstep( eyespheresize , 0.01 , d ); 
    col.rgb = mix(col.rgb , iriscol.rgb ,smoothstep(eyesphereoutlinesize , eyesphereoutlinesize  - blur ,d)); 
    float eyeblacksize = eyespheresize - 0.07 ; 
    col.rgb = mix(col.rgb , vec3(0.0),smoothstep(eyeblacksize , eyeblacksize - blur ,d)); 

    vec2  highlightpos = uv - vec2 ( - 0.1 , 0.1 ) ;
    float highlightsize = 0.07 ;  
    float highlight = smoothstep( highlightsize , highlightsize - blur , length (highlightpos)); 
    highlight += smoothstep( highlightsize * 0.65  , highlightsize* 0.65 - blur , length ( uv + vec2(-0.05 ,0.06 )));
    col.rgb = mix(col.rgb , vec3(1.0) , highlight );
    col.a = smoothstep(r, r-blur , d) ;
    return col ;
}
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 ;

    uv.x = abs(uv.x);
    vec4 head  = cricle(uv , 0.4 , 0.01 ) ;
    vec4 eye = eyecricle(within(uv, vec4(-0.02,-0.1,0.35,0.25)),0.3,0.01);
    vec4 mouth = Mouth(within(uv, vec4(-0.3 ,-0.35,0.29,-0.08)),0.4,0.01); 
    fragColor = mix(fragColor , head , head.a );
    fragColor = mix(fragColor , eye , eye.a );
    fragColor = mix(fragColor , mouth , mouth.a );
    return fragColor;
}

void main() {
    gl_FragColor = mainImage( gl_FragColor , gl_FragCoord );
}