unity shader实现冰冻效果

前言

大家好,本期我们来使用shader代码实现一下冰冻效果,带有冰锥下垂和冰锥向上生长的效果
效果展示

在这里插入图片描述
在这里插入图片描述

冰冻贴图

在这里插入图片描述

效果实现分析

  1. 渲染模式设置透明,开启常用的aphla混合,渲染队列可以是Geometry也可以是Transparent
Tags {
    
     "RenderType"="Transparent" "Queue"="Geometry" }
Blend SrcAlpha OneMinusSrcAlpha
  1. 使用2个Pass,第1个Pass渲染基础贴图和法线贴图,第2Pass使用1张冰冻贴图为主贴图
struct appdata
{
    
    
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
    float3 normal : NORMAL;
    float4 tangent : TANGENT;
};
struct v2f
{
    
    
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;
    float3 lightDir : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
v2f vert (appdata v)
{
    
    
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    TANGENT_SPACE_ROTATION;//使用这个宏必须在appdata定义normal,tangent
    o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;//将光照方向变换到切线空间
    return o;
}
fixed4 frag (v2f i) : SV_Target
{
    
    
    fixed4 col = tex2D(_MainTex, i.uv);
    float3 normal=UnpackNormal(tex2D(_BumpMap,i.uv));
    float diffuse=saturate(dot(normal,i.lightDir)*0.5+0.5);//漫反射
    return fixed4(col.rgb*diffuse,1);
}
  1. 第2个Pass在顶点着色器,在模型空间,将所有顶点沿着法线进行控制,模拟物体表面的一层冰
v.vertex.xyz+=v.normal*0.03*_Progress;//0.03是冰的厚度,_Progress是整体的冰冻进程
  1. 下垂冰锥的实现,在顶点着色器中,使用法线方向点乘向下(0,-1,0)的方向,
    也可以使用mask遮罩控制冰锥的范围
    这里2个方法结合起来使用
float n = dot(normalize(v.normal), (0,-1,0));
n = step(0.5, n);
half h = tex2Dlod(_IceMask, float4(v.uv, 0, 0)).r;
//在顶点着色器采样贴图使用tex2Dlod,注意第2个参数传入float4类型
v.vertex.xyz += n*(0,-1,0) * h * _IceCone*_Progress;
//_IceCone控制冰锥的长度
  1. 实现向上凸起的冰锥,使用mask遮罩控制大致的范围,会有大部分的向上冰块,使用一个噪声贴图,噪声贴图最好有很多黑白噪点,这样可以控制生成尖锐的冰锥.
    也可以不要噪声贴图,mask里面控制好要凸起的顶点
half _Ice = tex2Dlod(_NoiseIceExtendMask, float4(v.uv, 0, 0)).r;
half _IceNoise = tex2Dlod(_NoiseIceExtendNoise, float4(v.uv, 0, 0)).r;
_IceNoise = step(0.7, _IceNoise);
v.vertex.xyz +=  _Ice * v.normal * _IceInstensity2 * _IceNoise*_Progress;
//顶点沿着法线偏移,_IceInstensity2控制整体凸起冰锥的高度
  1. 实现从鼠标点击的地方开始产生冰冻效果,在uv空间中实现,可以实现怪物被一个冰锥击中,以击中点为中心扩散
    如果由于uv贴图的原因,效果不好
    可以在模型空间,以y轴为方向,向上下扩散
    或者,在模型空间,C#传入模型顶点位置xyz,以球形扩散
fixed2 uv = v.uv - float2(_start.x, _start.y);
o.freeze = 1-step(_Progress*1.42, length(uv));
//控制冰冻从哪个uv位置开始产生,_start
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
//将光照和视方向变换到切线空间

要物体与射线发生碰撞,给物体添加MeshCollider组件,可以简化网格,不要勾选凸面,否则生成的凸面没有顶点的uv信息
在这里插入图片描述

  1. C#脚本传入uv位置
if (Input.GetMouseButton(0))
{
    
    
    Ray ray = camera.ScreenPointToRay(Input.mousePosition);
    RaycastHit hit;
    if (Physics.Raycast(ray, out hit))
    {
    
    
        forstMaterial.SetVector("_start", new Vector4(hit.textureCoord.x, hit.textureCoord.y, 0, 0));
        if (!_animation.isPlaying)
        {
    
    
            _animation.Play(animation_name);
        }
    }
}
  1. 以下是第2个Pass的顶点着色器部分
fixed4 frostCol = tex2D(_FrostTex, i.uv) * _IceColor;//采样冰冻贴图作为第2个Pass的主贴图
fixed noiseMask = tex2D(_NoiseMask, i.uv).r + 0.3;//主冻贴图的噪声mask,控制a
noiseMask = saturate(noiseMask);
fixed a = _Progress * noiseMask * i.freeze;
  1. 漫反射部分
float3 normal = UnpackNormal(tex2D(_BumpMap, i.uv));
normal.xy *= _BumpScale;
fixed3 viewDir=normalize(i.viewDir);
float diffuse = saturate(dot(normal, i.lightDir) * 0.5 + 0.5);

fixed _Ice = tex2D(_IceMask, i.uv).r;//下巴处的冰锥亮度高
fixed3 halfDir=normalize(i.lightDir+viewDir);
diffuse+=_Ice*1.3*_Progress;//1.3控制亮度
  1. 镜面反射
normal=normalize(normal);
//_HasNormalTex没有法线贴图,镜面反射为0
//可以得到世界空间的法线和halfDir进行计算
fixed3 specular=pow(saturate(dot(halfDir,normal)),50)*_SpecularColor*_HasNormalTex;
  1. 菲涅尔效果,实现边缘区域颜色亮
fixed4 fresnelColor=saturate(pow(1-dot(normal,viewDir),_FresnelWidth))*_FresnelColor*_HasNormalTex;
  1. 最后颜色,基础颜色*漫反射系数+镜面反射+高光反射
frostCol = fixed4(frostCol.rgb * diffuse+specular+fresnelColor, a*0.5*_Progress);
  1. 兼容不同模型的方向,由于模型经过旋转,向下方向不一定是(0,-1,0)
    在材质面板自定义枚举兼容不同的模型
Properties
{
    
    
[KeywordEnum(NY,Y,NX,X,NZ,Z)] _DIR("向下的方向",Float)=0
}
...
CGPROGRAM
#pragma multi_compile _DIR_NY _DIR_Y _DIR_NX _DIR_X _DIR_NZ _DIR_Z

16.怎么制作遮罩贴图控制冰锥范围
在blender中导入模型
选择材质
在这里插入图片描述
在shader窗口新建mask贴图
在这里插入图片描述
在TexturePaint窗口,绘制区域,白色为冰锥区域
在这里插入图片描述
最后将遮罩贴图另存为即可

在这里插入图片描述

最终的代码

shader

Shader "Unlit/Frost"
{
    
    
    Properties
    {
    
    
        _MainTex ("基础贴图", 2D) = "white" {
    
    }
        _FrostTex ("冰冻贴图", 2D) = "white" {
    
    }
        _IceMask ("冰锥遮罩", 2D) = "white" {
    
    }//实现怪物下巴的冰锥
        _BumpMap("法线贴图",2D)="white"{
    
    }
        _NoiseMask("冰冻贴图整体噪声遮罩",2D)="white"{
    
    }
        [HDR]_IceColor("IceColor",Color)=(0,0,1,1)//冰冻的颜色
        _Progress("Progress",Range(0,1))=0.5//冰冻整体进程
        _IceCone("冰锥强度",Float)=0.5//下巴的冰锥强度
        _BumpScale("BumpScale",Float)=1//法线强度,没有法线设置为0
        _NoiseIceExtendMask("冰锥遮罩2",2D)="white"{
    
    }//实现身体的冰
        _NoiseIceExtendNoise("冰锥遮罩2噪声贴图",2D)="white"{
    
    }//实现身体的冰的遮罩
        _IceInstensity2("冰锥2强度",Float)=0
        [KeywordEnum(NY,Y,NX,X,NZ,Z)] _DIR("向下的方向",Float)=0
        [Enum(Yes,1,NO,0)] _HasNormalTex("有没有法线贴图",Float)=1
        _SpecularColor("镜面反射颜色",Color)=(1,0,0,1)
        [HDR]_FresnelColor("菲涅尔颜色",Color)=(1,0,0,1)
        _FresnelWidth("菲涅尔宽度",Float)=1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Transparent" "Queue"="Geometry" }
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha
        
        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            
            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                TANGENT_SPACE_ROTATION;
                o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed4 col = tex2D(_MainTex, i.uv);
                float3 normal=UnpackNormal(tex2D(_BumpMap,i.uv));
                float diffuse=saturate(dot(normal,i.lightDir)*0.5+0.5);
                return fixed4(col.rgb*diffuse,1);
            }
            ENDCG
        }
        
        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _DIR_NY _DIR_Y _DIR_NX _DIR_X _DIR_NZ _DIR_Z
            #include "UnityCG.cginc"


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

            struct v2f
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float freeze:TEXCOORD1;
                float3 lightDir :TEXCOORD2;
                float3 viewDir :TEXCOORD3;
            };

            half3 _Dir;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _FrostTex;
            sampler2D _NoiseMask;
            sampler2D _IceMask;
            sampler2D _BumpMap;
            fixed4 _IceColor;
            float _Progress;
            float _IceCone;
            float _BumpScale;
            sampler2D _NoiseIceExtendMask;
            float _IceInstensity2;
            sampler2D _NoiseIceExtendNoise;
            fixed4 _SpecularColor;
            fixed4 _FresnelColor;
            float _FresnelWidth;
            float _HasNormalTex;
            
            float4 _start;
            
            v2f vert(appdata v)
            {
    
    
                v2f o;
                #if _DIR_NY
                _Dir = half3(0, -1, 0);
                #elif _DIR_Y
                _Dir=half3(0,1,0);
                #elif _DIR_NX
                _Dir=half3(-1,0,0);
                #elif _DIR_X
                _Dir=half3(1,0,0);
                #elif _DIR_NZ
                _Dir=half3(0,0,-1);
                #else
                _Dir=half3(0,0,1);
                #endif
                
                v.vertex.xyz+=v.normal*0.03*_Progress;
                
                float n = dot(normalize(v.normal), _Dir);
                n = step(0.5, n);
                half h = tex2Dlod(_IceMask, float4(v.uv, 0, 0)).r;
                v.vertex.xyz += n*_Dir * h * _IceCone*_Progress;
                
                half _Ice = tex2Dlod(_NoiseIceExtendMask, float4(v.uv, 0, 0)).r;
                half _IceNoise = tex2Dlod(_NoiseIceExtendNoise, float4(v.uv, 0, 0)).r;
                _IceNoise = step(0.7, _IceNoise);
                v.vertex.xyz +=  _Ice * v.normal * _IceInstensity2 * _IceNoise*_Progress;
                
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                fixed2 uv = v.uv - float2(_start.x, _start.y);
                o.freeze = 1-step(_Progress*1.42, length(uv));
                TANGENT_SPACE_ROTATION;
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
    
    
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 frostCol = tex2D(_FrostTex, i.uv) * _IceColor;
                fixed noiseMask = tex2D(_NoiseMask, i.uv).r + 0.3;
                noiseMask = saturate(noiseMask);
                fixed a = _Progress * noiseMask * i.freeze;
                float3 normal = UnpackNormal(tex2D(_BumpMap, i.uv));
                normal.xy *= _BumpScale;
                fixed3 viewDir=normalize(i.viewDir);
                float diffuse = saturate(dot(normal, i.lightDir) * 0.5 + 0.5);
                fixed _Ice = tex2D(_IceMask, i.uv).r;//下巴处的冰锥亮度高
                fixed3 halfDir=normalize(i.lightDir+viewDir);
                diffuse+=_Ice*1.3*_Progress;
                normal=normalize(normal);
                fixed3 specular=pow(saturate(dot(halfDir,normal)),50)*_SpecularColor*_HasNormalTex;
                fixed4 fresnelColor=saturate(pow(1-dot(normal,viewDir),_FresnelWidth))*_FresnelColor*_HasNormalTex;
                frostCol = fixed4(frostCol.rgb * diffuse+specular+fresnelColor, a*0.5*_Progress);
                return frostCol;
            }
            ENDCG
        }
    }
}

C#代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FrostRaycast : MonoBehaviour
{
    
    
    private Camera camera;
    public Material forstMaterial;
    private Animation _animation;
    public string animation_name;
    private void Awake()
    {
    
    
        camera = Camera.main;
        _animation = GetComponent<Animation>();
    }
    void Update()
    {
    
    
        if (Input.GetMouseButton(0))
        {
    
    
            Ray ray = camera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
    
    
                forstMaterial.SetVector("_start", new Vector4(hit.textureCoord.x, hit.textureCoord.y, 0, 0));
                Debug.Log($"hit.gameObject:{
      
      hit.collider.name}");
                Debug.Log($"coord.:{
      
      hit.textureCoord.x},{
      
      hit.textureCoord.y}");
                if (!_animation.isPlaying)
                {
    
    
                    _animation.Play(animation_name);
                }
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_58047420/article/details/134903648