Unity3D ShaderLab 内发光防护罩

内发光防护罩

1、利用模型法线和视角方向得到内轮廓发光效果。
2、利用深度图来做与其他物体相交效果。

思路

内轮廓发光效果

只需要知道模型每个三角面的法向量和相机到三角面顶点的向量,通过1.0 - dot(normal, viewDir)就能得到外轮廓发光效果。

相交效果

已知需要渲染物体的深度信息,通过与_CameraDepthTexture纹理中的深度值进行一些计算或者判定即可完成相交的效果。

缺陷

内轮廓发光缺陷

在Unity3D的Cube中,此方案将存在缺陷;是由于面之间的法向量互相垂直导致。

缺陷解决方案

  1. 修改模型的法线向量,取各面法向量总和的均值。(可能会导致其他Pass渲染异常)
  2. 使用后处理解决,一般能够完美的解决;会带来额外的渲染性能开销。

相交效果缺陷

不同视角方向会存在_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
        }
    }
}

效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值