内发光防护罩
1、利用模型法线和视角方向得到内轮廓发光效果。
2、利用深度图来做与其他物体相交效果。
思路
内轮廓发光效果
只需要知道模型每个三角面的法向量和相机到三角面顶点的向量,通过1.0 - dot(normal, viewDir)
就能得到外轮廓发光效果。
相交效果
已知需要渲染物体的深度信息,通过与_CameraDepthTexture
纹理中的深度值进行一些计算或者判定即可完成相交的效果。
缺陷
内轮廓发光缺陷
在Unity3D的Cube中,此方案将存在缺陷;是由于面之间的法向量互相垂直导致。
缺陷解决方案
- 修改模型的法线向量,取各面法向量总和的均值。(可能会导致其他Pass渲染异常)
- 使用后处理解决,一般能够完美的解决;会带来额外的渲染性能开销。
相交效果缺陷
不同视角方向会存在_CameraDepthTexture深度值为0的情况
,从而导致断层或者其他问题。
缺陷解决方案
目前未想到使用深度图对应的解决方案。可以考虑使用其他方式解决,例如:不使用深度图,传入一个法向量和面原点解决。
实现
Shader "Hidden/ForceField"
{
Properties
{
_MainColor("Main Color", Color) = (1,1,1,1)
_RimPower("Rim Power", Range(0, 1)) = 1
_IntersectionPower("Intersect Power", Range(0, 1)) = 0
}
SubShader
{
Pass
{
// 关闭深度写入
ZWrite Off
// 关闭剔除
Cull Off
// 开个blend
Blend SrcAlpha One
// 渲染队列
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldViewDir : TEXCOORD1;
float4 screenPos : TEXCOORD2;
float4 eyeZ : TEXCOORD3;
};
// 深度图
sampler2D _CameraDepthTexture;
// 护罩颜色
fixed4 _MainColor;
// 轮廓强度
float _RimPower;
// 相交长度
float _IntersectionPower;
v2f vert (appdata v)
{
v2f o;
// vertex模型顶点转裁剪坐标系
o.vertex = UnityObjectToClipPos(v.vertex);
// normal模型法线转世界坐标系
o.worldNormal = normalize(UnityObjectToWorldDir(v.normal));
// 获得世界坐标系中 模型顶点 -> 相机顶点 的向量
o.worldViewDir = normalize(UnityWorldSpaceViewDir(mul(unity_ObjectToWorld, v.vertex)));
// 返回齐次坐标系下的点, 范围是[0, w]; 其中,w不是viewport的width (ps: 需要与tex2Dproj函数配套使用)
o.screenPos = ComputeScreenPos(o.vertex);
// 将v.vertex与model和view矩阵相乘, 得到相机到物体的z坐标, 为view坐标系下
COMPUTE_EYEDEPTH(o.eyeZ);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// dot(worldNormal, worldViewDir)两个向量的夹角越大, 值越小(cos(t)函数)
// 1.0 - saturate(abs(dot(i.worldNormal, i.worldViewDir)))得到了反向颜色值, 也就是夹角越小, 值越大(1-cos(t))
float rim = pow(1.0 - saturate(abs(dot(i.worldNormal, i.worldViewDir))), _RimPower) * 0.5;
// UNITY_PROJ_COORD(a)应该是预留的一个接口, 官方解释为大部分平台将返回入参值
// SAMPLE_DEPTH_TEXTURE_PROJ(tex, uv)内部调用tex2Dproj(tex, uv), tex2Dproj会将(uv = uv / w)
// LinearEyeDepth(depth)将返回depth在view坐标系下的表示, 源码中的_ZBufferParams是相机中far和near两个参数表达式的结果值
float screenZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
// 相交计算, saturate(1.0 - abs(物体的深度值 - 深度缓存的深度值)) => saturate限制到[0, 1]
float intersect = saturate(1.0 - abs(i.eyeZ - screenZ)) * _IntersectionPower;
// 取两者中的最大
return max(rim, intersect) * _MainColor;
}
ENDCG
}
}
}