写在前面:写shader写累了就写写博客吧,每天都是充满bug的一天> <
使用版本:Unity3D 2019.4 (LTS) (LTS意思是长期支持,尽量选择LTS版本,兼容性比较好)
效果示意
Inspector
Glass
注:所有Texture均来自冯乐乐《UnityShader入门精要》项目配套源码1
代码示意
Glass.shader
1
Shader "Glass"
{
Properties
{
_BumpMap("Normal Map",2D) = "bump"{
}
_Distortion("Distortion",range(0,100)) = 10
//折射系数
_RefractAmount("Refract Amount",range(0,1)) = 1
[KeywordEnum(Cubemap, MatCap)] _Reflect("Reflect", Float) = 0
_Cubemap("Environment Map",cube) = "_Skybox"{
}
}
SubShader
{
Tags {
"RenderType" = "Opaque" "Queue" = "Transparent" "LightMode" = "Always"}
// GrabPass{"_RefractionTex"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _REFLECT_CUBEMAP _REFLECT_MATCAP
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
float4 tangent:TANGENT;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD4;
float4 TtoW0:TEXCOORD1;
float4 TtoW1:TEXCOORD2;
float4 TtoW2:TEXCOORD3;
};
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _Cubemap;
float _Distortion;
fixed _RefractAmount;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.uv = TRANSFORM_TEX(v.uv, _BumpMap);
//得到屏幕采样坐标
o.scrPos = ComputeGrabScreenPos(o.pos);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
float3 worldBinormal = cross(worldTangent, worldNormal) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
float3x3 TtoW = float3x3(i.TtoW0.xyz, i.TtoW1.xyz, i.TtoW2.xyz);
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 tanNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
fixed3 worldNormal = mul(TtoW, tanNormal);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal);
float2 offset = tanNormal.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy += offset;
fixed3 refractCol = tex2Dproj(_RefractionTex, i.scrPos).xyz;
fixed3 reflectDir = reflect(-worldViewDir, worldNormal);
fixed4 cubemapCol = texCUBE(_Cubemap, reflectDir);
fixed4 matcapLookup = tex2D(_RefractionTex, viewNormal.xy * 0.5 + 0.5);
fixed3 reflectCol;
#if _REFLECT_CUBEMAP
reflectCol = cubemapCol.rgb;
#elif _REFLECT_MATCAP
reflectCol = matcapLookup.rgb;
#endif
fixed3 color = refractCol * _RefractAmount + reflectCol * (1 - _RefractAmount);
return fixed4(color,1.0);
}
ENDCG
}
}
}
CommandBufferBlurRefraction.cs
2
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
[ExecuteInEditMode]
public class CommandBufferBlurRefraction : MonoBehaviour
{
private Camera m_Cam;
// private Dictionary<Camera, CommandBuffer> m_Cameras = new Dictionary<Camera, CommandBuffer>();
public void OnWillRenderObject()
{
var cam = Camera.current;
if (!cam)
return;
cam.RemoveAllCommandBuffers();
CommandBuffer buf = null;
buf = new CommandBuffer();
buf.name = "Grab screen and blur";
//新建一张纹理,得到对应的纹理ID
int screenCopyID = Shader.PropertyToID("_RefractionTex");
//初始化这张纹理,其中纹理的大小为摄像机视野大小
buf.GetTemporaryRT(screenCopyID, -1, -1, 0, FilterMode.Bilinear);
//将当前的渲染目标纹理拷贝到你的纹理上
buf.Blit(BuiltinRenderTextureType.CurrentActive, screenCopyID);
buf.SetGlobalTexture("_RefractionTex", screenCopyID);
buf.ReleaseTemporaryRT(screenCopyID);
cam.AddCommandBuffer(CameraEvent.AfterSkybox, buf);
}
}
知识点
Unity Shader自定义材质面板的小技巧3
适用场景
仅仅想在材质面板里定义少数变量,不想多写一个C#函数从头自定义材质面板的时候。
提到的drawer
//in Properties
[KeywordEnum(Cubemap, MatCap)] _Reflect("Reflect", Float) = 1
……
//in Pass
#pragma multi_compile _REFLECT_CUBEMAP _REFLECT_MATCAP
//in frag
#if _REFLECT_CUBEMAP
reflectCol = cubemapCol.rgb;
#elif _REFLECT_MATCAP
reflectCol = matcapLookup.rgb * cubemapCol.rgb;
#endif
Unity官方文档原文:
KeywordEnum allows you to choose which of a set of shader keywords to enable. It displays a float as a popup menu, and the value of the float determines which shader keyword Unity enables. Unity enables a shader keyword with the name . Up to 9 names can be provided.(uppercase property name)_(uppercase enum value name)
大意就是,这个drawer画了一个选项框,最多支持9个选项,并且可以用浮点数初始化选项。浮点数默认从零开始。
在Properties中初始化后,我们还要在Pass中引入选项,这样才能在后文中定义选择该选项时Shader的行为,具体格式如上。
在本shader中,我在片元着色器上使用#if和#elif来控制选择不同选项时shader的不同行为。最后记得#endif,不然会出现Syntax Error。
使用CommandBuffer来抓取RT
原本我是使用了GrabPass,但这个在实际项目中是禁止使用的。换成了CommandBuffer后效果也是一样的。参考的CommandBuffer功能太多,在帮助之下删成了上文那个样子o> <o
捋一下思路就是,获取相机->清除相机buffer->新建一个临时纹理并初始化,将抓到的贴图装进去->拷贝临时纹理到指定纹理上->新建相机缓冲区并载入->清除临时纹理
这时候就有人问了,如果不载入指定纹理会发生什么呢?我也不知道qaq,试了一下效果都是一样的。
Matcap(未完待续)
关于Shader,Cubemap的部分在《UnityShader入门精要》中有详细讲解,这里不再复述,主要是Matcap的部分,作为第一次接触的方法,详细了解一下。
我对Matcap的理解是,它其实就是一种采样方式,根据法线的xy分量在贴图中选取对应的点来渲染。注意要把法线从模型空间转移到观察空间,再取观察空间中法线的xy分量进行计算。如果使用法线贴图的话,流程就是切线空间->模型空间->观察空间,代码如下:
fixed3 tanNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
fixed3 worldNormal = mul(TtoW, tanNormal);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal);
Matcap的采样方式如下,之所以viewNormal.xy * 0.5 + 0.5
是为了将法线分量范围从[-1,1]转换为[0,1],也就是uv坐标的范围。
fixed4 matcapLookup = tex2D(_RefractionTex, viewNormal.xy * 0.5 + 0.5);
当然,我的代码还是有问题的,问题在于Mapcap如果是用的长方体的话,法线在同一个面是相同的,只能采样到同一个位置,看起来就是纯色的,所以需要用到曲率,但我现在还没有时间,等到有时间了再更新~先mark一个解决办法在这里。
结束语:居然写了三个小时……一边写一边捋思路真的太难了。有空把之前写过的也捋一下吧,不然真的会忘qaq