《着色器和屏幕特效》读书笔记第五章-顶点函数

本书版本为“占红来 译”版,笔记会持续更新,有错误的地方欢迎指正,谢谢!

引言

着色器不仅能决定物体的外观,还能重新定义物体的形状。

三维模型和一堆三角形的集合,三角形的每个顶点可以包含一些基础数据,通过这些数据就能渲染出该模型了。本文要讲述的是:如何获取这部分信息并使用之。

在表面着色器中访问顶点的颜色

一个顶点包含的信息:

  1. float4类型的颜色值;
  2. float3类型的顶点位置;
  3. float3类型的顶点法线方向。

实现访问顶点颜色的代码:

Shader "BookShaders/5-2 VertColor" {
    Properties {
        _MainTint("Global Color Tint",Color) = (1,1,1,1)
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        //告诉Unity我们将在着色器中添加一个顶点函数。
        #pragma surface surf Lambert vertex:vert

        #pragma target 3.0

        float4 _MainTint;

        //Input结构里的数据是其他地方输出到它的数据,比如vertColor是vert()中输出到它的,
        //这实现了提取顶点颜色的目的,还要使surf()能访问之。所以这需要加一个vertColor。
        struct Input {
            float2 uv_MainTex; 
            float4 vertColor;
        };

        //appdata_full是内置结构体,用来存储顶点信息。
        void vert(inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input, o);//相当于把全部都参数初始化了。
            //appdata_full内置结构体的一个参数color。
            o.vertColor = v.color;
        }

        //SurfaceOutput也是内置的结构体,用于输出信息到光照模型。surf()获取数据的两种方式:
        //1.通过“IN.XXXX”来使用Input结构里的数据;2.直接调用Properties中的属性值。
        void surf (Input IN, inout SurfaceOutput o) 
        {
            //Albedo是SurfaceOutput内置结构体的一个参数。
            o.Albedo = IN.vertColor.rgb*_MainTint.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

导入Unity的模型的默认材质是不会让顶点颜色显示出来的,我们必须自己编写这个着色器获取到顶点颜色,并将它们展示在模型表面。

使用默认材质时:
这里写图片描述

使用含有该着色器的自定义的材质:
这里写图片描述

表面着色器中的顶点动画

访问顶点的每个位置并使用正弦波来动态修改每个顶点的位置,可用于制作飘扬的旗帜和海浪等效果。

实现访问顶点位置并修改的代码:

Shader "BookShaders/5-3 VertexAnimation"
{
    Properties
    {
        _MainTex("Base (RGB)", 2D) = "white" {}
        _tintAmount("Tint Amount", Range(0,1)) = 0.5
        _ColorA("Color A", Color) = (1,1,1,1)
        _ColorB("Color B", Color) = (1,1,1,1)
        _Speed("Wave Speed", Range(0.1, 80)) = 5
        _Frequency("Wave Frequency", Range(0, 5)) = 2
        //振幅
        _Amplitude("Wave Amplitude", Range(-1, 1)) = 1
    }

    SubShader
    {
        Tags{ "RenderType" = "Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert vertex:vert

        sampler2D _MainTex;
        float4 _ColorA;
        float4 _ColorB;
        float _tintAmount;
        float _Speed;
        float _Frequency;
        float _Amplitude;
        float _OffsetVal;

        struct Input
        {
            float2 uv_MainTex;
            float3 vertColor;
        };

        void vert(inout appdata_full v, out Input o)
        {
            //相当于把参数初始化了。
            UNITY_INITIALIZE_OUTPUT(Input, o);
            float time = _Time * _Speed;
            //使用正弦波对顶点位置进行修改。
            float waveValueA = sin(time + v.vertex.x * _Frequency)*_Amplitude;

            //在y方向起伏
            v.vertex.xyz = float3(v.vertex.x, v.vertex.y + waveValueA, v.vertex.z);
            //对网格顶点的法线进行微调,为了更逼真。让每个顶点的法线更偏向X轴的走向。
            v.normal = normalize(float3(v.normal.x + waveValueA, v.normal.y, v.normal.z));
            //可将下面三个数修改为(1,1,1)或(0,0,0)或(-1,-1,-1),就知道其作用了。
            o.vertColor = float3(waveValueA, waveValueA, waveValueA);
        }

        void surf(Input IN, inout SurfaceOutput o)
        {
            half4 c = tex2D(_MainTex, IN.uv_MainTex);
            //lerp()函数混合两种颜色。凸起的地方(值为01)是_ColorB,凹陷的地方(值为0到-1)是_ColorA。
            //使用vertColor、vertColor.r、vertColor.g、vertColor.b都是同样的效果,因为值都为waveValueA。
            float3 tintColor = lerp(_ColorA, _ColorB, IN.vertColor).rgb;
            o.Albedo = c.rgb * (tintColor * _tintAmount);
            o.Alpha = c.a;
        }
        ENDCG
        }
        FallBack "Diffuse"
}

未使用该着色器:
这里写图片描述
使用了该着色器:
这里写图片描述

挤压模型

通过法线挤压技术来控制模型的胖瘦(即调整其几何结构),比如:同一个模型(模型必须得有法线)可做成一个瘦瘦的僵尸或一个胖子。

实现法线挤压的代码:(特别简单,就是加一句:沿着法线增或减~)

Shader "BookShaders/5-4 Extrusion" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        //用于表示挤压程度,0是不变,0到-0.00015是瘦,00.00015是胖。
        //0.00015已经算是这个模型的阈值了,不然这模型就不是人样了。。。
        _Amount("Extrusion Amount",Range(-0.00015,0.00015)) = 0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert vertex:vert

        #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        float _Amount;
        fixed4 _Color;

        void vert(inout appdata_full v)
        {
            //沿着法线方向进行挤压
            v.vertex.xyz += v.normal*_Amount;
        }

        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

正常图:
这里写图片描述

瘦的:
这里写图片描述

胖的:
这里写图片描述

再放出下面这张图,大家应该能全部理解了:
这里写图片描述

实现雪花着色器

步骤:先调整几何结构,再给表面着白色的雪。(顶点控制函数vert()会在表面着色函数surf()之前被执行,vert()的位置并不影响它的执行顺序。)

代码:

Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _Bump ("Bump", 2D) = "bump" {}
    _Snow ("Snow Level", Range(0,1) ) = 0//_Snow决定多大的角度才算是朝着下雪的方向,即覆雪的范围。
    _SnowColor ("Snow Color", Color) = (1.0,1.0,1.0,1.0)//雪的颜色
    _SnowDirection ("Snow Direction", Vector) = (0,1,0)//下雪的方向(正上方)
    _SnowDepth ("Snow Depth", Range(0,0.3)) = 0.1//雪的厚度
}

sampler2D _MainTex;
sampler2D _Bump;
float _Snow;
float4 _SnowColor;
float4 _SnowDirection;
float _SnowDepth;

struct Input {
    float2 uv_MainTex;
    float2 uv_Bump;
    float3 worldNormal; INTERNAL_DATA
};

void surf (Input IN, inout SurfaceOutput o) {
    half4 c = tex2D (_MainTex, IN.uv_MainTex);
    //UnpackNormal(x)接受一个fixed4的输入x,并将x转换为所对应的法线值(fixed3)。
    o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump));

    //将三角形的法线从相对坐标转换为世界坐标后,再与下雪方向点积。
    if (dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1,-1,_Snow)) {
        o.Albedo = _SnowColor.rgb;
    } else {
        o.Albedo = c.rgb;
    }
    o.Alpha = c.a;
}

//和之前积雪的运算其实比较类似,判断点积大小来决定是否需要扩大模型以及确定模型扩大的方向。
//surf是依靠三角形的法线和颜色rgba,vert是依靠顶点的法线和方向xyz。
void vert (inout appdata_full v) {
    float4 sn = mul(transpose(_Object2World) , _SnowDirection);
    if(dot(v.normal, sn.xyz) >= lerp(1,-1, (_Snow * 2) / 3)) {
        v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;
    }
}

积雪效果:
这里写图片描述

积雪效果的详细制作请见这篇文章

实现体积爆炸效果

爆炸无非就是一团很热的气体,也就是模拟流体呗~很多游戏通过粒子效果来模拟爆炸,用一些火焰、烟雾和碎片粒子来做,而这种实现方式并不真实,所以有了体积(是三维对象)爆炸这个概念。

补充:顶点函数只能修改顶点,而其他非顶点部分的信息通过插值得到。

要实现爆炸效果,需要一张坡度纹理(包含爆炸所需的所有颜色);需要一张噪声纹理(在着色器中获取随机数的最简单的方法:对噪声纹理进行采样)。

步骤一:通过顶点函数(这里的顶点函数是法线挤压的一个变种)修改几何结构;
步骤二:利用表面函数进行着色。

为了让爆炸更炫酷,还可以加一些粒子喔~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值