Unity shader achieves freezing effect

Preface

Hello everyone, in this issue we will use shader code to achieve a freezing effect, with the effect of ice cones drooping and ice cones growing upward
Effect display

Insert image description here
Insert image description here

Freeze map

Insert image description here

Effect realization analysis

  1. Set the rendering mode to transparent, enable the commonly used aphla blending, and the rendering queue can be Geometry or Transparent.
Tags {
    
     "RenderType"="Transparent" "Queue"="Geometry" }
Blend SrcAlpha OneMinusSrcAlpha
  1. Use 2 Passes, the first Pass renders the basic texture and normal map, and the second Pass uses a frozen texture as the main texture
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. The second Pass is in the vertex shader. In the model space, all vertices are controlled along the normal lines to simulate a layer of ice on the surface of the object.
v.vertex.xyz+=v.normal*0.03*_Progress;//0.03是冰的厚度,_Progress是整体的冰冻进程
  1. To implement the drooping ice cone, in the vertex shader, use the normal direction point multiplication in the downward (0,-1,0) direction,
    You can also use mask Control the range of the ice pick
    Here are two methods used together
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. To achieve an upward convex ice cone, use a mask to control the approximate range. There will be most of the upward ice cubes. Use a noise map. It is best for the noise map to have a lot of black and white noise, so that the generation of sharp ice can be controlled. Ice pick.
    You can also eliminate the noise map and control the vertices to be raised in the 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. Achieve the freezing effect starting from the place clicked by the mouse. It is implemented in the UV space. The monster can be hit by an ice pick and spread around the hit point.
    If due to UV The effect is not good because of the texture
    You can spread it up and down in the model space with the y-axis as the direction
    Or, in the model space, C# passes in the model vertices Position xyz, spread in a spherical shape
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;
//将光照和视方向变换到切线空间

If you want the object to collide with the ray, add the MeshCollider component to the object to simplify the mesh. Do not check the convex surface, otherwise the generated convex surface will have no uv information of the vertices.
Insert image description here

  1. C# script passes in uv location
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. The following is the vertex shader part of Pass 2
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. Diffuse reflection part
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. reflection of mirror
normal=normalize(normal);
//_HasNormalTex没有法线贴图,镜面反射为0
//可以得到世界空间的法线和halfDir进行计算
fixed3 specular=pow(saturate(dot(halfDir,normal)),50)*_SpecularColor*_HasNormalTex;
  1. Fresnel effect to achieve brighter colors in edge areas
fixed4 fresnelColor=saturate(pow(1-dot(normal,viewDir),_FresnelWidth))*_FresnelColor*_HasNormalTex;
  1. Final color, base color * diffuse reflection coefficient + specular reflection + specular reflection
frostCol = fixed4(frostCol.rgb * diffuse+specular+fresnelColor, a*0.5*_Progress);
  1. Compatible with the directions of different models. Since the model has been rotated, the downward direction is not necessarily (0,-1,0)
    Customize the enumeration in the material panel to be compatible with different models
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. How to make a mask map to control the ice cone range
Import the model in blender
Select the material
Insert image description here
Create a new mask map in the shader window
Insert image description here
In the TexturePaint window, draw the area, white is the ice pick area
Insert image description here
Finally, save the mask map as

Insert image description here

final code

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# code

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);
                }
            }
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_58047420/article/details/134903648