时间:2021年11月30日09:12:27
本笔记仅作为《Shader入门精要》的部分要点回顾和补充,重点放在表面着色器和ASE的使用上
Ch7.透明效果
P125 模板测试
- 不管通过还是未通过测试,在测试结束之后都可以对缓存中的模板值做操作
语法:
Stencil
{
Ref referenceValue
ReadMask readMask
WriteMask writeMask
Comp comparisonFunction
Pass stencilOperation
Fail stencilOperation
ZFail stencilOperation
}
Ch8.表面着色器的基础概念
P134 组织结构
表面着色器编译指令的语法结构为:
#pragma surface surfaceFunction lightModel [optionalparams]
1)surface:声明所使用的Shader是表面着色器
2)surfaceFunction:声明表面着色器的函数名称,被称为表面函数,一般使用surf作为表面函数的名称
3)lightModel:声明所使用的的光照模型。Unity内置四种光照模型,分别为:
- 非物理光照模型:Lambert和BlinnPhong
- 物理光照模型:Standard和StandardSpecular
4)[optionalparams]:其他可选参数
P134 编译指令中的可选参数
自行查询书籍了解
P137 表面函数的语法结构
void surf(Input IN, inout SurfaceOutput o)
{
//表面函数代码
}
1.表面函数输入结构体
使用者可以通过下表中的变量直接获取到相关数据,然后传入表面函数中进行计算
变量 | 说明 |
---|---|
float2 uv_texName、float2 uv2_texName | uv关键词后接纹理名称,获取模型某套uv坐标 |
float3 viewDir | 摄像机视角方向(世界空间),没有被标准化 |
使用COLOR语义定义的float4变量 | 插值后的逐顶点颜色 |
float4 screenPos | 屏幕空间坐标,可用于反射或屏幕空间特效,但不适用于GrabPass,需要使用ComputeGrabScreenPos函数单独计算uv |
float3 worldPos | 世界空间坐标 |
float3 worldRefl | 世界空间反射向量,前提是没有修改表面法线 o.Normal |
float3 worldNormal | 世界空间法线线路,前提是没有修改表面法线 o.Normal |
float3 worldRefl; INTERNAL_DATA | 如果表面法线 o.Normal 进行了修改,在表面函数中通过WorldReflectionVector(IN, o.Normal)得到基于法线贴图的世界空间反射向量 |
float3 worldNormal; INTERNAL_DATA | 如果表面法线 o.Normal 进行了修改,在表面函数中通过 WorldNormalVector(IN, o.Normal)得到基于法线贴图的世界空间法线向量 |
2.表面函数输出结构体
Lambert和BlinnPhong使用SurfaceOutput结构体输出
struct SurfaceOutput
{
fixed3 Albedo; //漫反射颜色
fixed3 Normal; //切线空间法线
fixed3 Emission; //自发光
half Specular; //镜面反射指数,范围0~1
fixed Gloss; //镜面反射强度
fixed Alpha; //透明通道
};
金属工作流输出结构体
struct SurfaceOutputStandard
{
fixed3 Albedo; //漫反射颜色
fixed3 Normal; //切线空间法线
fixed3 Emission; //自发光
half Metallic; //0表示非金属,1表示金属
half Smoothness; //0表示非常粗糙,1表示非常光滑
half Occlusion; //环境光遮蔽,默认为1
fixed Alpha; //透明通道
};
高光工作流输出结构体
struct SurfaceOutputStandard
{
fixed3 Albedo; //漫反射颜色
fixed3 Normal; //切线空间法线
fixed3 Emission; //自发光
//half Metallic; //0表示非金属,1表示金属
half Smoothness; //0表示非常粗糙,1表示非常光滑
half Occlusion; //环境光遮蔽,默认为1
fixed Alpha; //透明通道
};
Ch9.编写表面着色器
P141 使用法线贴图
Shader "Surface Shader/Normal Map"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_Normal ("Normal Map", 2D) = (1,1,1,1)
_Bumpiness ("Bumpiness", Range(0, 1)) = 0
}
SubShader
{
CGPROGRAM
#pragma surface surf Lambert
struct Input
{
float2 uv_MainTex;
float2 uv_Normal;
};
sampler2D _MainTex;
fixed4 _Color;
sampler2D _Normal;
fixed _Bumpiness;
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
//采样法线贴图并解包
fixed3 n = UnpackNormal(tex2D(_Normal, IN.uv_Normal));
n *= float3(_Bumpiness, _Bumpiness, 1);
o.Normal = n;
}
ENDCG
}
FallBack "Diffuse"
}
P146 使用顶点修改函数
Shader "Surface Shader/Vertex Modify"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_Expansion ("Expansion", Range(0, 0.1)) = 0
}
SubShader
{
CGPROGRAM
#pragma surface surf Lambert vertex:vert
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
fixed _Expansion;
//顶点修改函数,输入/输出 appdata_full 结构体
void vert (inout appdata_full v)
{
v.vertex.xyz += v.normal * _Expansion;
}
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
P148 自定义光照函数
Shader "Surface Shader/Custom Lambert"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
}
SubShader
{
CGPROGRAM
#pragma surface surf CustomLambert
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
}
//自定义光照函数
half4 LightingCustomLambert(SurfaceOutput s, half3 lightDir, half atten)
{
fixed NdotL = saturate(dot(s.Normal, lightDir));
half4 c;
c.rgb = s.Albedo * _LightColor0 * NdotL * atten;
c.a = s.Alpha;
return c;
}
ENDCG
}
FallBack "Diffuse"
}
P149 最终颜色修改函数
Shader "Surface Shader/Final Color Modify"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_ColorTint ("Color Tint", Color) = (1,1,1,1)
}
SubShader
{
CGPROGRAM
//声明最终颜色修改函数为 ModifyColor
#pragma surface surf Lambert finalcolor:ModifyColor
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
fixed4 _ColorTint;
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
}
//最终颜色修改函数
void ModifyColor (Input IN, SurfaceOutput o, inout fixed4 color)
{
color *= _ColorTint;
}
ENDCG
}
FallBack "Diffuse"
}
P151 使用曲面细分函数
曲线细分语法:
添加编译指令:tessellate:FunctionName
float4 FunctionName()
{
return tess; //曲面细分等级
}
固定数量的曲面细分
#pragma surface surf Lambert tesellate:tessellation vertex:height addshadow
half _Tessellation; //参考值 1 ~ 32
//曲面细分函数
float4 tessellation()
{
return _Tessellation;
}
基于边长的曲面细分
会根据不同长度的边分别对应不同的曲面细分等级,边越长细分等级越高,从而使模型的边长在屏幕上看起来一致
#pragma surface surf Lambert tesellate:tessellateEdge vertex:height addshadow
half _EdgeLength; //边的平均长度,越小细分程度越高 参考值 1 ~ 32
//曲面细分函数
float4 tessellateEdge(appdata_full v0, appdata_full v1, appdata_full v2)
{
//调用基于边长的曲面细分函数
return UnityEdgeLengthBasedTess(v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
视锥剔除曲面细分
在基于边长的基础上,额外判断顶点是否在摄像机的视锥体内,超出视锥体范围的顶点将会被剔除
#pragma surface surf Lambert tesellate:tessellateCull vertex:height addshadow
half _EdgeLength; //边的平均长度,越小细分程度越高 参考值 1 ~ 32
float _MaxHeight; //参考值 0 ~ 0.5
//曲面细分函数
float4 tessellateCull(appdata_full v0, appdata_full v1, appdata_full v2)
{
//调用基于边长的曲面细分函数
return UnityEdgeLengthBasedTessCull(v0.vertex, v1.vertex, v2.vertex, _EdgeLength, _MaxHeight);
}
基于距离的曲面细分
- d < minDist,细分等级为tess
- minDist < d < maxDist:逐渐降低
- d > maxDist:细分等级保持为1
#pragma surface surf Lambert tesellate:tessellateDistance vertex:height addshadow
half _MinDistance;
half _MaxDistance;
half _Tessellation; //参考值 1 ~ 32
//曲面细分函数
float4 tessellateDistance(appdata_full v0, appdata_full v1, appdata_full v2)
{
//基于距离的曲线细分
return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, _MinDistance, _MaxDistance, _Tessellation);
}
Phong 曲面细分
Phong细分会使新生成的顶点沿着原始顶点的平均法线方向偏移一段距离,使模型表面变得更加光滑
- Phong曲面细分单独使用是没有效果的,需要结合曲面细分函数才会生效
#pragma surface surf Lambert tesellate:tessellation tessphong:_Phong
half _Tessellation; //参考值 1 ~ 32
fixed _Phong; //不加会报错,值越大,越光滑 0~1
//曲面细分函数
float4 tessellation()
{
return _Tessellation;
}
P164 透明度混合
Shader "Surface Shader/Transparent"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_Color ("Color(RGB-A)", Color) = (1,1,1,1)
}
SubShader
{
Tags {"Queue" = "Transparent"}
CGPROGRAM
//使用alpha指令开启透明效果
#pragma surface surf Lambert alpha
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
fixed4 Color;
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a * _Color.a;
}
ENDCG
}
FallBack "Diffuse"
//个人感觉 Fallback "Transparent/VertexLit" 更合适
}
P166 透明度测试
Shader "Surface Shader/Transparent"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_AlphaTest ("Alpha Test", Range(0,1)) = 0
}
SubShader
{
Tags {"Queue" = "AlphaTest"}
CGPROGRAM
//添加alphatest指令开启透明测试
//添加addshadow 指令开启
#pragma surface surf Lambert alphatest:_AlphaTest addshadow
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
//不用再次声明_AlphaTest照样没问题(和Phong曲面细分不同)
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a * _Color.a;
}
ENDCG
}
FallBack "Diffuse"
//个人感觉 Fallback "Transparent/Cutout/VertexLit" 更合适
}
Ch10.Image Effect
P168 GrabPass
GrabPass可以获取模型对应的屏幕纹理
Shader "Custom/GrabPass"
{
Properties
{
_GrayScale ("Gray Scale", Range(0, 1)) = 0
}
SubShader
{
Tags {"Queue" = "Transparent"}
//调用GrabPass,并定义抓取图像的名称
GrabPass {"_ScreenTex"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 grabPos : TEXCOORD0;
};
v2f vert(float4 vertex : POSITION)
{
v2f o;
o.pos = UnityObjectToClipPos(vertex);
//计算抓取图像在屏幕上的位置
o.grabPos = ComputeGrabScreenPos(o.pos);
return o;
}
fixed _GrayScale;
//声明抓取图像
sampler2D _ScreenTex;
half4 frag (v2f i) : SV_TARGET
{
//采样抓取图像
//tex2Dproj相当于将纹理的xy分量除以w分量之后再对纹理贴图进行采样
half4 src = tex2Dproj(_ScreenTex, i.grabPos);
half grayscale = Luminance(src.rgb);
half4 dst = half4(grayscale, grayscale, grayscale, 1);
return lerp(src, dst, _GrayScale);
}
}
}
}
P187 自定义后处理堆栈
Shader
Shader "Hidden/BSC - HLSL"
{
HLSLINCLUDE
#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
//属性声明
TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
half _Brightness;
half _Saturation;
half _Contrast;
float4 Frag(VaryingsDefault i) : SV_TARGET
{
//采样 RenderTexture
float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
//亮度
color.rgb *= _Brightness;
//饱和度
float luminance = dot(color.rgb, float3(0.2126729, 0.7151522, 0.0721750));
color.rgb = lerp(luminance, color.rgb, _Saturation);
//对比度
half3 grayColor = half3(0.5, 0.5, 0.5);
color.rgb = lerp(grayColor, color.rgb, _Contrast);
return color;
}
ENDHLSL
SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex VertDefault
#pragma fragment Frag
ENDHLSL
}
}
}
C#脚本
using System;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;
//参数模块:继承PostProcessEffectSettings
//参数需序列化
[Serializable]
//参数①:关联脚本的渲染模块
//参数②:后期处理操作应用的对象类型或所处时间阶段
// - BeforeTransparent:后处理效果只会应用于不透明物体,会在透明物体渲染之前执行
// - BeforeStack:后处理效果会在内置的堆栈之前执行,如抗锯齿、景深、色调映射等前面
// - AfterStack:后处理效果会在内置的堆栈之后执行,如果有抗锯齿效果,会在其之前执行
//参数③:后期处理效果在下拉列表中的名称,使用"/"可以创建子目录
[PostProcess(typeof(BSCRenderer), PostProcessEvent.AfterStack, "Custom/BSC")]
public sealed class BSC : PostProcessEffectSettings
{
//开放属性
[Range(0f, 2f), Tooltip("Brightness effect intensity.")]
public FloatParameter Brightness = new FloatParameter { value = 1f };
[Range(0f, 2f), Tooltip("Saturation effect intensity.")]
public FloatParameter Saturation = new FloatParameter { value = 1f };
[Range(0f, 2f), Tooltip("Contrast effect intensity.")]
public FloatParameter Contrast = new FloatParameter { value = 1f };
}
//渲染模块:继承PostProcessEffectRenderer<T>
public sealed class BSCRenderer : PostProcessEffectRenderer<BSC>
{
public override void Render(PostProcessRenderContext context)
{
// 查找Shader文件
var sheet = context.propertySheets.Get(Shader.Find("Hidden/BSC-HLSL"));
//传递属性到Shader
sheet.properties.SetFloat("_Brightness", settings.Brightness);
sheet.properties.SetFloat("_Saturation", settings.Saturation);
sheet.properties.SetFloat("_Contrast", settings.Contrast);
context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
}
}
Setting中常用变量类型
类型 | 描述 |
---|---|
FloatParameter | 浮点型 |
BoolParameter | 布尔型 |
ColorParameter | 颜色 |
Vector2Parameter | 二维向量 |
Vector3Parameter | 三维向量 |
Vector4Parameter | 四维向量 |
TextureParameter | 纹理贴图,常用的默认值有:None、Black、White、Transparent |
最终效果
Ch11.自定义材质面板
P197 Toggle
默认关键词
- 例句:
[Toggle] _Invert color?", Float) = 0
- 以上例句会被Unity默认设置为
_INVERT_ON
- 以上例句会被Unity默认设置为
自定义关键词
- 例句:
[Toggle(ENABLE_FANCY)] _Fancy ("Fancy?", Float) = 0
- 关键词:
ENABLE_FANCY
- 关键词:
P197 Enum
内置枚举类型
- 例句:
[Enum(UnityEngine.Rendering.BlendMode)] _Blend ("Blend mode", Float) = 1
自定义枚举类型
- 一个枚举最多只能自定义7个名称/数值对
- 例句:
[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 0
P198 KeywordEnum
和Enum类似,只不过关键词枚举会有与之对应的Shader关键词
- 例句:
[KeywordEnum(None, Add, Multiply)] _Overlay ("Overlay mode", Float) = 0
- 这三个选项对应的Shader关键词分别为:
_OVERLAY_NONE
、_OVERLAY_ADD
和_OVERLAY_MULTIPLY
- 这三个选项对应的Shader关键词分别为:
P198 编译指令定义关键词
定义了ToggleDrawer或KeywordEnumDrawer之后,如果想要正常使用,还需要在编译指令中声明Shader关键词
两种编译指令,不同关键词之间用空格隔开
shader_feature
:只会为材质使用到的关键词生成变体,没有使用到的关键词不会生成变体,因此无法在运行的时候通过脚本切换效果- 例如,
#pragma shader_feature _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY
- 例如,
#pragma shader_feature _INVERT_ON
- 例如,
multi_compile
:会为所有关键词生成变体,因此可以在运行的时候通过脚本切换效果- 例如,
#pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY
- 例如,
P199 PowerSlider
指数滑动条
- 例子:
[PowerSlider(3.0)] _Brightness ("Birghtness", Range(0.01, 1)) = 0.1
- 这是一个以3为指数的滑动条
P200 IntRange
整数滑动条
- 例子:
[IntRange] _Alpha ("Alpha", Range(0, 255)) = 100
Ch13.初级案例
P228 1.流光效果
原理:通过引入_Time对uv坐标进行偏移,实现流光效果
Shader "Samples/Light Flow"
{
Properties
{
_Tex("Texture", 2D) = "white" {}
_Color("Color", Color) = (0, 1, 1, 1)
// 关键词枚举,0为x方向,1为y方向
[KeywordEnum(X,Y)] _DIRECTION("Flow Direction", float) = 0
_Speed("Flow Speed", float) = 1
}
SubShader
{
Tags {"RenderType" = "Transparent" "Queue" = "Transparent"}
Blend One One
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 定义枚举关键词
#pragma shader_feature _DIRECTION_X _DIRECTION_Y
#include "unityCG.cginc"
struct v2f
{
float2 texcoord : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _Tex;
float4 _Tex_ST;
fixed4 _Color;
float _Speed;
v2f vert (appdata_base v)
{
v2f o;
o.texcoord = TRANSFORM_TEX(v.texcoord, _Tex);
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
float2 texcoord;
// 判断流动方向
#if _DIRECTION_X
texcoord = float2(i.texcoord.x + _Time.x * _Speed,
i.texcoord.y);
#elif _DIRECTION_Y
texcoord = float2(i.texcoord.x,
i.texcoord.y + _Time.x * _Speed);
#endif
return tex2D(_Tex, texcoord) * _Color;
}
ENDCG
}
}
}
P234 2.描边效果
原理:通过两个Pass,第一个Pass关闭深度写入,绘制一遍沿法线扩张的描边色模型;第二个Pass正常绘制。由于第一个Pass关闭了深度写入,如果渲染队列为Geometry,可能会导致在模型身后的几何体将描边颜色覆盖住,进而产生错误的效果,因此将整个Shader的渲染队列延迟到Transparent
亮点亦是缺点:第二个Pass会完全覆盖第一个Pass非描边部分,这会导致描边细节丢失,而仅仅绘制模型外围一圈描边,模型内部没有任何描边
Shader "Samples/Outline"
{
Properties
{
[Header(Texture Group)]
[Space(10)]
_Albedo ("Albedo", 2D) = "white" {}
[NoScaleOffset]_Specular ("Specular (RGB-A)", 2D) = "black" {}
[NoScaleOffset]_Normal ("Normal", 2D) = "bump" {}
[NoScaleOffset]_AO ("Ambient Occlusion", 2D) = "white" {}
[Header(Outline Properties)]
[Space(10)]
_OutlineColor ("Outline Color", Color) = (1,0,1,1)
_OutlineWidth ("Outline Width", Range(0, 0.1)) = 0.01
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue" = "Transparent"}
//---------- Outline Layer ----------
Pass
{
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
};
fixed4 _OutlineColor;
fixed _OutlineWidth;
v2f vert(appdata_base v)
{
v2f o;
v.vertex.xyz += v.normal * _OutlineWidth;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
//---------- Regular Layer ----------
CGPROGRAM
#pragma surface surf StandardSpecular fullforwardshadows
struct Input
{
float2 uv_Albedo;
};
sampler2D _Albedo;
sampler2D _Specular;
sampler2D _Normal;
sampler2D _AO;
void surf (Input IN, inout SurfaceOutputStandardSpecular o)
{
fixed4 c = tex2D (_Albedo, IN.uv_Albedo);
o.Albedo = c.rgb;
fixed4 specular = tex2D (_Specular, IN.uv_Albedo);
o.Specular = specular.rgb;
o.Smoothness = specular.a;
o.Normal = UnpackNormal(tex2D (_Normal, IN.uv_Albedo));
o.Occlusion = tex2D (_AO, IN.uv_Albedo);
}
ENDCG
}
}
P241 3.遮挡半透明
原理:两个Pass渲染,被遮挡部分深度测试设为Greater,中间半透,边缘发亮;未被遮挡部分正常绘制
- 遮挡半透明不需要显示后面的物体,就不必更改渲染队列了
Shader "Samples/X-Ray"
{
Properties
{
[Header(The Blocked Part)]
[Space(10)]
_Color ("X-Ray Color", Color) = (0,1,1,1)
_Width ("X-Ray Width", Range(1, 2)) = 1
_Brightness ("X-Ray Brightness",Range(0, 2)) = 1
[Header(The Normal Part)]
[Space(10)]
_Albedo("Albedo", 2D) = "white"{}
[NoScaleOffset]_Specular ("Specular (RGB-A)", 2D) = "black"{}
[NoScaleOffset]_Normal ("Nromal", 2D) = "bump"{}
[NoScaleOffset]_AO ("AO", 2D) = "white"{}
}
SubShader
{
Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}
//---------- The Blocked Part ----------
Pass
{
//深度测试设为Greater,大于则通过
ZTest Greater
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 vertexPos : SV_POSITION;
float3 viewDir : TEXCOORD0;
float3 worldNor : TEXCOORD1;
};
v2f vert(appdata_base v)
{
v2f o;
o.vertexPos = UnityObjectToClipPos(v.vertex);
o.viewDir = normalize(WorldSpaceViewDir(v.vertex));
o.worldNor = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 _Color;
fixed _Width;
half _Brightness;
float4 frag(v2f i) : SV_Target
{
// Fresnel算法
half NDotV = saturate(dot(i.worldNor, i.viewDir));//中间1,边缘0
NDotV = pow(1 - NDotV, _Width) * _Brightness;//中间0,边缘1,0的范围大
fixed4 color;
color.rgb = _Color.rgb;
color.a = NDotV;
return color;
}
ENDCG
}
//---------- The Normal Part ----------
CGPROGRAM
#pragma surface surf StandardSpecular
#pragma target 3.0
struct Input
{
float2 uv_Albedo;
};
sampler2D _Albedo;
sampler2D _Specular;
sampler2D _Normal;
sampler2D _AO;
void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
o.Albedo = tex2D(_Albedo, IN.uv_Albedo).rgb;
fixed4 specular = tex2D(_Specular, IN.uv_Albedo);
o.Specular = specular.rgb;
o.Smoothness = specular.a;
o.Normal = UnpackNormal(tex2D(_Normal, IN.uv_Albedo));
}
ENDCG
}
}
P249 4.三平面映射
原理:使用世界空间中X、Y、Z三个方向的坐标屏幕对纹理进行采样
- X方向上显示colorYZ
- Y方向上显示colorXZ
- Z方向上显示colorXY
Shader "Samples/Tri-Planar Mapping"
{
Properties
{
_Tiling ("Tiling", float) = 1
[NoScaleOffset]_Albedo ("Albedo", 2D) = "white" {}
[NoScaleOffset]_Normal ("Normal", 2D) = "bump" {}
_Bumpiness ("Bumpiness", Range(0.01, 10)) = 1
}
SubShader
{
CGPROGRAM
#pragma surface surf Lambert fullforwardshadows
struct Input
{
float3 worldPos;
float3 worldNormal;
INTERNAL_DATA
};
float _Tiling;
sampler2D _Albedo;
sampler2D _Normal;
half _Bumpiness;
void surf (Input IN, inout SurfaceOutput o)
{
//使用世界空间坐标进行采样
float3 texCoord = IN.worldPos * _Tiling;
//根绝法向量与坐标方向的契合度,来计算采样权重
// -------------------- Mask --------------------
float3 normal = abs(WorldNormalVector(IN, o.Normal));
//如果法向方向与(1,0,0)契合度越高,则maskX越大
fixed maskX = saturate(dot(normal, fixed3(1, 0, 0)));
fixed maskY = saturate(dot(normal, fixed3(0, 1, 0)));
// -------------------- Albedo --------------------
//用xy坐标采样(同xy,不同z的颜色会全部一致)
fixed4 colorXY = tex2D (_Albedo, texCoord.xy);
fixed4 colorYZ = tex2D (_Albedo, texCoord.yz);
fixed4 colorXZ = tex2D (_Albedo, texCoord.xz);
fixed4 c;
//maskX越大,说明法线方向越朝向X轴,此时采样的结果就越接近colorYZ
c = lerp(colorXY, colorYZ, maskX);
c = lerp(c, colorXZ, maskY);
o.Albedo = c.rgb;
// -------------------- Normal --------------------
fixed3 normalXY = UnpackNormal(tex2D(_Normal, texCoord.xy));
fixed3 normalYZ = UnpackNormal(tex2D(_Normal, texCoord.yz));
fixed3 normalXZ = UnpackNormal(tex2D(_Normal, texCoord.xz));
fixed3 n;
n = lerp(normalXY, normalYZ, maskX);
n = lerp(n, normalXZ, maskY);
o.Normal = n * half3(_Bumpiness, _Bumpiness, 1);
}
ENDCG
}
FallBack "Diffuse"
}
P258 5.MatCap效果
原理:使用法线向量对纹理进行采样
分析:模型某一点的单位化后的法线向量,投影到XY平面,用投影后的坐标进行采样。因为投影之前,所有法线向量最多构成一个单位球,因此投影后的XY坐标必不可能为(1,1)之类的点(单位圆以外的点),所以你会发现Matcap贴图长成这个样子
最终效果:
Shader "Samples/MatCap"
{
Properties
{
[NoScaleOffset]_MatCap ("MatCap", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue" = "Geometry"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 texcoord : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MatCap;
v2f vert (appdata_base v)
{
v2f o;
// 使用UNITY_MATRIX_MV的逆转置矩阵
// 变换非统一缩放物体的法线向量
float4 normal = mul(UNITY_MATRIX_IT_MV, float4(v.normal, 0));
o.texcoord = normalize(normal.xyz).xy;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 范围 [-1, 1] => [0, 1]
float2 texcoord = i.texcoord * 0.5 + 0.5;
return tex2D(_MatCap, texcoord);
}
ENDCG
}
}
FallBack "Diffuse"
}
P263 6.物体切割效果
原理:利用世界坐标配合透明度测试实现物体切割效果
Shader "Samples/Object Cutting"
{
Properties
{
[Header(Textures)] [Space(10)]
[NoScaleOffset] _Albedo ("Albedo", 2D) = "white" {}
[NoScaleOffset] _Reflection ("Specular_Smoothness", 2D) = "black" {}
[NoScaleOffset] _Normal ("Normal", 2D) = "bump" {}
[NoScaleOffset] _Occlusion ("Ambient Occlusion", 2D) = "white" {}
[Header(Cutting)] [Space(10)]
[KeywordEnum(X, Y, Z)] _Direction ("Cutting Direction", Float) = 1
[Toggle] _Invert ("Invert Direction", Float) = 0
}
SubShader
{
Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" }
Cull Off
CGPROGRAM
#pragma surface surf StandardSpecular addshadow fullforwardshadows
#pragma target 3.0
#pragma multi_compile _DIRECTION_X _DIRECTION_Y _DIRECTION_Z
sampler2D _Albedo;
sampler2D _Reflection;
sampler2D _Normal;
sampler2D _Occlusion;
//外部传入模型的Position
float3 _Position;
fixed _Invert;
struct Input
{
float2 uv_Albedo;
float3 worldPos;
fixed face : VFACE; //VFACE语义,当正面朝向摄像机时返回正值,否则返回负值
};
void surf (Input i, inout SurfaceOutputStandardSpecular o)
{
fixed4 col = tex2D(_Albedo, i.uv_Albedo);
o.Albedo = i.face > 0 ? col.rgb : fixed3(0,0,0);//背面默认渲染成纯黑
// 判断切割方向
#if _DIRECTION_X
//参数①:参考值
//参数②:实际值
//实际值 >= 参考值,返回1;否则返回0
col.a = step(_Position.x, i.worldPos.x);
#elif _DIRECTION_Y
col.a = step(_Position.y, i.worldPos.y);
#else
col.a = step(_Position.z, i.worldPos.z);
#endif
// 判断是否反转切割方向
col.a = _Invert? 1 - col.a : col.a;
clip(col.a - 0.001);
//使用反射向量采用高光及粗糙度贴图
fixed4 reflection = tex2D(_Reflection, i.uv_Albedo);
o.Specular = i.face > 0 ? reflection.rgb : fixed3(0,0,0);
o.Smoothness = i.face > 0 ? reflection.a : 0;
o.Normal = UnpackNormal(tex2D(_Normal, i.uv_Albedo));
o.Occlusion = tex2D(_Occlusion, i.uv_Albedo);
}
ENDCG
}
}
C#脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class GetPosition : MonoBehaviour
{
public GameObject CuttingPosition;
private Material Material;
private Vector3 Center = new Vector3(0, 0, 0);
void Start()
{
//获取当前物体材质
Material = this.GetComponent<Renderer>().sharedMaterial;
}
void Update()
{
if (CuttingPosition)
//获取CuttingPosition的坐标并传递给Shade
Material.SetVector("_Position", CuttingPosition.transform.position);
else
Material.SetVector("_Position", Center);
}
}
Ch14.进阶案例
P274 1.消融效果
原理:根据噪点图使用透明度测试
Shader "Samples/Dissolve"
{
Properties
{
// -------------------- PBS Textures --------------------
[Header(PBS Textures)]
[Space(10)]
[NoScaleOffset]_Albedo("Albedo", 2D) = "white" {}
[NoScaleOffset]_Specular("Specular_Smoothness", 2D) = "black" {}
[NoScaleOffset]_Normal("Normal", 2D) = "bump" {}
[NoScaleOffset]_AO("AO", 2D) = "white" {}
// -------------------- Dissolve Properties --------------------
[Header(Dissolve Properties)]
[Space(10)]
_Noise("Dissolve Noise", 2D) = "white" {}
_Dissolve("Dissolve", Range(0, 1)) = 0
[NoScaleOffset]_Gradient("Edge Gradient", 2D) = "black" {}
_Range("Edge Range", Range(2, 100)) = 6
_Brightness("Brightness", Range(0, 10)) = 1
}
SubShader
{
Tags
{
"RenderType"="TransparentCutout" "Queue" = "AlphaTest"
}
CGPROGRAM
#pragma surface surf StandardSpecular addshadow fullforwardshadows
struct Input
{
float2 uv_Albedo;
float2 uv_Noise;
};
sampler2D _Albedo;
sampler2D _Specular;
sampler2D _Normal;
sampler2D _AO;
sampler2D _Noise;
fixed _Dissolve;
sampler2D _Gradient;
float _Range;
float _Brightness;
void surf (Input IN, inout SurfaceOutputStandardSpecular o)
{
// Clip Mask
//采样噪点图,r通道为alpha值 [0,1]
fixed noise = tex2D(_Noise, IN.uv_Noise).r;
//[0,1] -> [-1,1] 起始值 0:不溶解 1:完全溶解
fixed dissolve = _Dissolve * 2 - 1;
//dissolve为-1,则noise - dissolve >= 1 > 0.5,不溶解
//dissolve为0,则noise - dissolve = noise ∈ [0,1],可能溶解也可能不溶解
//dissove为1,则noise - dissolve <= 0,完全溶解
fixed mask = saturate(noise - dissolve);
clip(mask - 0.5);
// Burn Effect
//放大误差,倍率由_Range指定。这样做可以得到mask接近0.5的部分(以0~1渐变形式给出,越远值越为1)
fixed texcoord = saturate(mask * _Range - 0.5 * _Range);
o.Emission = tex2D(_Gradient, fixed2(texcoord, 0.5)) * _Brightness;
fixed4 c = tex2D (_Albedo, IN.uv_Albedo);
o.Albedo = c.rgb;
fixed4 specular = tex2D(_Specular, IN.uv_Albedo);
o.Specular = specular.rgb;
o.Smoothness = specular.a;
o.Normal = UnpackNormal(tex2D(_Normal, IN.uv_Albedo));
o.Occlusion = tex2D(_AO, IN.uv_Albedo);
}
ENDCG
}
}
P283 2.动态液体
原理:使用透明度测试,利用模型中心点,剔除上半部分。再使用sin实现波纹效果。
- 瓶内液体的制作就是将瓶身缩小一点后即可,嵌套进瓶身即可
缺点:从瓶子底部看会穿帮
Shader "Samples/Dynamic Liquid"
{
Properties
{
_Color("Color", Color) = (1, 0, 0, 1)
_Specular("Specular_Smoothness", Color) = (0, 0, 0, 0)
_Level("Liquid Level", float) = 0 //液体等级,控制液体高度
// 定义关键词枚举的名称
[KeywordEnum(X,Z)] _Direction("Ripple Direction", float) = 0
_Speed("Ripple Speed", float) = 1 //时间倍率
_Height("Ripple Height", float) = 1 //控制正弦曲线幅度
}
SubShader
{
Tags{"RenderType" = "Transparent" "Queue" = "Transparent"}
Blend DstColor SrcColor
ZWrite Off
CGPROGRAM
#pragma surface surf StandardSpecular noshadow
// 定义关键词
#pragma shader_feature _DIRECTION_X _DIRECTION_Z
struct Input
{
float3 worldPos;
};
fixed4 _Color;
fixed4 _Specular;
half _Level;
half _Speed;
half _Height;
void surf (Input IN, inout SurfaceOutputStandardSpecular o)
{
// 液面效果
//模型中心点,模型空间 -> 世界空间
float3 pivot = mul(unity_ObjectToWorld, float4(0, 0, 0, 1));
//pivot.y - IN.worldPos.y表示,如果点在模型中心上方返回负值,否则返回正值
//通过_Level进行值的修正
float liquid = pivot.y - IN.worldPos.y + _Level * 0.01;
// 波纹效果
//书上代码感觉不妥,如果xz很大(∞),ripple值∈(-∞,+∞)远超过[0,1],根本无法体现波纹效果
//float3 ripple = sin(_Time.y * _Speed) * _Height * IN.worldPos;
//正确的波纹效果应该要和中心点做差值,这样求得的结果是离中心点越远的点波纹效果越明显
float3 ripple = sin(_Time.y * _Speed) * _Height * (IN.worldPos - pivot);
// 根据波纹的不同方向进行判断
#if _DIRECTION_X
liquid += ripple.x;
#else
liquid += ripple.z;
#endif
// 像素剔除
liquid = step(0, liquid);
clip(liquid - 0.001);
o.Albedo = _Color.rgb;
o.Specular = _Specular.rgb;
o.Smoothness = _Specular.a;
}
ENDCG
}
}
P293 3.Billboard效果
原理:重建顶点的模型坐标系。z轴方向为观察方向,x轴为向右方向,y轴由叉乘得出,通过矩阵变换将所有顶点变换至新坐标系下
分类:
- Spherical:球体模式。能够在水平和垂直方向旋转,角度完全朝向摄像机。例如,粒子特效
- Cylindrical:圆柱体模式。只在水平方向旋转,垂直方向固定。例如,远距离的树
本质就是z轴方向的y轴是否恒为0,恒为0就是圆柱体,否则为球体
Shader "Samples/Billboard"
{
Properties
{
[NoScaleOffset] _Tex ("Texture", 2D) = "white" {}
_Tint ("Tine", Color) = (1, 1, 1, 1)
[KeywordEnum(Spherical, Cylindrical)] _Type ("Type", float) = 0
}
SubShader
{
Tags
{
"RenderType" = "Transparent"
"Queue" = "Transparent"
"DisableBatching" = "True"
}
Blend OneMinusDstColor One
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 声明枚举的关键词
#pragma shader_feature _TYPE_SPHERICAL _TYPE_CYLINDRICAL
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
sampler2D _Tex;
fixed4 _Tint;
v2f vert (appdata v)
{
v2f o;
// 计算面片朝向摄像机的前方向量
float3 forward = mul(unity_WorldToObject,
float4(_WorldSpaceCameraPos, 1)).xyz;
// 判断Billboard的类型
#if _TYPE_CYLINDRICAL
forward.y = 0;
#endif
forward = normalize(forward);
// 当摄像机完全在面片正上方或者正下方的时候,旋转临时的上方向量
float3 up = abs(forward.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
float3 right = normalize(cross(forward, up));
up = normalize(cross(right, forward));
// 将顶点在新的坐标系上移动位置
float3 vertex = v.vertex.x * right + v.vertex.y * up;
o.vertex = UnityObjectToClipPos(vertex);
o.texcoord = v.texcoord;
return o;
}
float4 frag (v2f i) : SV_Target
{
return tex2D(_Tex, i.texcoord) * _Tint;
}
ENDCG
}
}
}
P302 4.序列帧动画
原理:根据时间,计算uv,采样纹理
Shader "Samples/Sequence Animation"
{
Properties
{
[NoScaleOffset] _Tex ("Sequence Image", 2D) = "white" {}
_Tint ("Tint", Color) = (1, 1, 1, 1)
_Row ("Row Amount", float) = 1
_Column ("Column Amount", float) = 1
_Rate ("Animation Rate", float) = 1
}
SubShader
{
Tags
{
"RenderType" = "Transparent"
"Queue" = "Transparent"
"DisableBatching" = "True"
}
Blend OneMinusDstColor One
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
sampler2D _Tex;
fixed4 _Tint;
float _Row;
float _Column;
float _Rate;
v2f vert (appdata v)
{
v2f o;
// ---------- Billboard 部分 ----------
float3 forward = mul(unity_WorldToObject,
float4(_WorldSpaceCameraPos, 1)).xyz;
forward.y = 0;
forward = normalize(forward);
float3 up = abs(forward.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
float3 right = normalize(cross(forward, up));
up = normalize(cross(right, forward));
float3 vertex = v.vertex.x * right + v.vertex.y * up;
o.vertex = UnityObjectToClipPos(vertex);
// ---------- 序列帧 部分 ----------
// 计算序列帧的行索引和列索引
float time = floor(_Time.y * _Rate);
float row = floor(time / _Column); //商为行索引
float column = fmod(time, _Column); //余数为列索引
// 计算序列帧的纹理坐标
float texcoordU = (v.texcoord.x + column) / _Column;
//因为纹理是从左上角开始的,实际采用的V值是从0开始的
//由于纹理设为Repeat,所求V初始值需要向负方向偏移 1 / _Row
float texcoordV = (v.texcoord.y - 1 - row) / _Row;
o.texcoord = float2(texcoordU, texcoordV);
return o;
}
float4 frag (v2f i) : SV_Target
{
return tex2D(_Tex, i.texcoord) * _Tint;
}
ENDCG
}
}
}
P312 5.卡通风格效果
原理:Half Lambert采样Ramp,描边,边缘高光,光照模型
Shader "Samples/Toon"
{
Properties
{
[Header(Diffuse)]
[Space(10)] _Albedo ("Albedo", 2D) = "white" {}
_Ramp ("Toon Ramp", 2D) = "white" {}
[Header(Rim)]
[Space(10)][HDR] _RimColor ("Rim Color", Color) = (0,2,2,1)
_RimWidth ("Rim Width", Range(0,1)) = 0
_RimFalloff ("Rim Falloff", Range(0.01,10)) = 1
[Header(Outline)]
[Space(10)] _OutlineColor ("Outline Color", Color) = (0,0,0,0)
_OutlineWidth ("Outline, Width", Float) = 0.02
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
// ---------- Outline 部分----------
Pass
{
//使用先渲染背面的描边,描边细节会更丰富
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _OutlineColor;
half _OutlineWidth;
float4 vert (appdata_base v) : SV_POSITION
{
v.vertex.xyz += v.normal * _OutlineWidth;
return UnityObjectToClipPos(v.vertex);
}
float4 frag () : SV_Target
{
return _OutlineColor;
}
ENDCG
}
// ---------- Surface 部分----------
CGPROGRAM
#pragma surface surf Toon
sampler2D _Albedo;
struct Input
{
float2 uv_Albedo;
float3 worldNormal;
float3 viewDir;
};
// 自定义的表面函数输出结构体
struct SurfaceOutputToon
{
//四个必选项
half3 Albedo;
half3 Normal;
half3 Emission;
fixed Alpha;
// 将Input结构体包含进来,为了获取worldNormal和viewDir
Input SurfaceInput;
// 内置的全局照明结构体,为了获取光照信息
UnityGIInput GIdata;
};
//UnityGIInput的定义
//struct UnityGIInput
//{
// float3 worldPos;
// half3 worldViewDir;
// half atten;
// half3 ambient;
// float4 probeHDR[2];
//};
void surf (Input i, inout SurfaceOutputToon o)
{
o.SurfaceInput = i;
o.Albedo = tex2D(_Albedo, i.uv_Albedo);
}
//GI函数:格式 "Lighting + 光照模型名称_GI"
void LightingToon_GI (inout SurfaceOutputToon s, UnityGIInput GIdata, UnityGI gi)
{
s.GIdata = GIdata;
}
sampler2D _Ramp;
half4 _RimColor;
fixed _RimWidth;
half _RimFalloff;
//UnityGI定义
//struct UnityGI
//{
// UnityLight light;
// UnityIndirect indirect;
//};
//直接光照信息
//struct UnityLight
//{
// half3 color;
// half3 dir;
//};
//间接光照信息
//struct UnityIndirect
//{
// half3 diffuse;
// half3 specular;
//};
//自定义光照函数
half4 LightingToon (SurfaceOutputToon s, UnityGI gi)
{
// 重新赋值,方便后续调用结构体内的变量
UnityGIInput GIdata = s.GIdata;
Input i = s.SurfaceInput;
// 使用内置的UnityGI_Base()函数计算GI
gi = UnityGI_Base(GIdata, GIdata.ambient, i.worldNormal);
// 将光照转为Ramp
fixed NdotL = dot(i.worldNormal, gi.light.dir);
fixed2 rampTexcoord = float2(NdotL * 0.5 + 0.5, 0.5);
fixed3 ramp = tex2D(_Ramp, rampTexcoord).rgb;
// 计算漫反射
half3 diffuse = s.Albedo * ramp * _LightColor0.rgb *
(GIdata.atten + gi.indirect.diffuse);
// 计算边缘高光
fixed NdotV = dot(i.worldNormal , i.viewDir);
fixed rimMask = pow((1.0 - saturate((NdotV + _RimWidth))), _RimFalloff);
half3 rim = saturate(rimMask * NdotL) * _RimColor *
_LightColor0.rgb * GIdata.atten;
// 输出漫反射与边缘高光的和
return half4(diffuse + rim, 1);
}
ENDCG
}
FallBack "Diffuse"
}
P326 6.夜视仪后期处理
原理:镜头扭曲(书上只给了公式)、调色、四周暗角效果、随机生成的噪点
Shader
Shader "Hidden/Night Vision"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
}
SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
half2 uv : TEXCOORD0;
half4 screenPos : TEXCOORD1;
};
v2f vert (appdata_img v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
// 通过裁切空间坐标得到屏幕空间坐标
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
sampler2D _MainTex;
half _Distortion;
half _Scale;
fixed _Brightness;
fixed _Saturation;
fixed _Contrast;
fixed4 _Tint;
// 暗角属性
half _VignetteFalloff;
half _VignetteIntensity;
// 噪点属性
sampler2D _Noise;
half _NoiseAmount;
half _RandomValue;
fixed4 frag (v2f i) : SV_Target
{
// 镜头扭曲
fixed2 center = i.uv - 0.5;
half radius2 = pow(center.x, 2) + pow(center.y, 2);
half distortion = 1 + sqrt(radius2) * radius2 * _Distortion;
half2 uvScreen = center * distortion * _Scale + 0.5;
fixed4 screen = tex2D(_MainTex, uvScreen);
// 亮度、饱和度、对比度
screen += _Brightness;
fixed4 luminance = Luminance(screen.rgb).xxxx;
screen = lerp(luminance, screen, _Saturation);
fixed4 gray = fixed4(0.5,0.5,0.5,1);
screen = lerp(gray, screen, _Contrast);
// 着色
screen *= _Tint;
// 暗角
// 得到屏幕中心到该点的距离,最终效果应该是中间黑,呈四周渐变逐渐到灰白(最大约为0.707)
half circle = distance(i.screenPos.xy, fixed2(0.5,0.5));
//利用pow扩大黑暗区域,利用1-结果,得到中间大面积白,四周灰
fixed vignette = 1 - saturate(pow(circle, _VignetteFalloff));
//整体压暗,四周值小,暗得更快。最终效果就是中间白,四周黑
screen *= pow(vignette, _VignetteIntensity);
// 噪点颗粒
float2 uvNoise = i.uv * _NoiseAmount;
//利用脚本传来的随机_RandomValue进行uv偏移
uvNoise.x += sin(_RandomValue);
uvNoise.y -= sin(_RandomValue + 1);
fixed noise = tex2D(_Noise, uvNoise).r;
screen *= noise;
return screen;
}
ENDCG
}
}
}
C#脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]
public class NightVision : MonoBehaviour
{
public Shader EffectShader;
[Header("Basic Properties")]
[Range(-2, 2)] public float Distortion = 0.5f;
[Range(0.01f, 1)] public float Scale = 0.5f;
[Range(-1, 1)] public float Brightness = 0;
[Range(0, 2)] public float Saturation = 1;
[Range(0, 2)] public float Contrasrt = 1;
public Color Tint = Color.black;
[Header("Advanced Properties")]
[Range(0, 10)] public float VignetteFalloff = 1;
[Range(0, 100)] public float VignetteIntensity = 1;
public Texture2D Noise;
[Range(0, 10)] public float NoiseAmount = 1;
private float RandomValue;
private Material currentMaterial;
Material EffectMaterial
{
get
{
if (currentMaterial == null)
{
currentMaterial = new Material(EffectShader)
{
hideFlags = HideFlags.HideAndDontSave
};
}
return currentMaterial;
}
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (EffectMaterial != null)
{
EffectMaterial.SetFloat("_Distortion", Distortion);
EffectMaterial.SetFloat("_Scale", Scale);
EffectMaterial.SetFloat("_Brightness", Brightness);
EffectMaterial.SetFloat("_Saturation", Saturation);
EffectMaterial.SetFloat("_Contrast", Contrasrt);
EffectMaterial.SetColor("_Tint", Tint);
EffectMaterial.SetFloat("_VignetteFalloff", VignetteFalloff);
EffectMaterial.SetFloat("_VignetteIntensity", VignetteIntensity);
if (Noise != null)
{
EffectMaterial.SetTexture("_Noise", Noise);
EffectMaterial.SetFloat("_NoiseAmount", NoiseAmount);
EffectMaterial.SetFloat("_RandomValue", RandomValue);
}
Graphics.Blit(source, destination, EffectMaterial);
}
else Graphics.Blit(source, destination);
}
private void Update()
{
//随机生成范围中的数值
RandomValue = Random.Range(-3.14f, 3.14f);
}
}