首页 > 其他分享 >Unity Shader深度图的应用,手把手教你写出可以正确计算并且渲染出二次元角色边缘光的着色器(含代码,图片说明和原理)

Unity Shader深度图的应用,手把手教你写出可以正确计算并且渲染出二次元角色边缘光的着色器(含代码,图片说明和原理)

时间:2024-10-22 14:51:47浏览次数:3  
标签:深度图 图片说明 float Shader 边缘 CameraDepthTexture output input float2

梦开始的地方

相信大家看番的时候,都注意到了,很多时候,在角色周围有一圈光晕

旧版《魔术快斗》剧照

《新蔷薇少女》剧照 

我们将这种光晕,称之为边缘光

边缘光是描边的一种,动画师之所以加入边缘光,是为了凸现角色轮廓,使得角色区别于背景

不少游戏也有着这种边缘光

游戏《鸣潮》

 

游戏《原神》

经过观察,我们不难发现,边缘光与暗部的交界轮廓,与角色外轮廓基本一致

画师画边缘光时,有时会在绘图软件中,直接复制角色的图层,运用正片叠底发光等效果,线性减淡,以达到边缘光的效果(将图层涂黑便是描边)

画师的工程文件截图

但是在unity里面,我们要检测获取角色的轮廓呢?

或许我们可以通过片原的法向量与视线向量的夹角判断?

当该片原法向量与视线向量的夹角,大于某个角度(如80)度,则视为该片原处于边缘

我们不妨看看效果

效果图1

 可以看到,在脸颊,出现了十分诡异的边缘光,显然这不是我们想要的效果

正片开始

我们可以通过unity中的深度图,来获取角色轮廓

参考文章:屏幕空间等距边缘光 - 哔哩哔哩

在unity shader中,其实存储了物体在屏幕空间中的深度信息,我们将存储了这个信息的图,称之为深度图(其中的数学原理,上述文章讲的已经非常非常好了,想知道数学原理的可以直接看参考文献)

我们先来看看深度图长什么样

unity中的深度图

深度图是一张灰度图,深度越小,越黑,反之越白,而角色与其背景的深度在大部分时候是不一样的,这意味着我们可以通过屏幕深度来获取角色轮廓。

判断轮廓的思路

我们将深度图的片元的uv坐标向左(或者向右)进行偏移,将偏移前后的深度值(在深度图中体现为RGB值)进行比较,如果差值大于某个值,则视为该片元处于边缘

采样过程(片原向左偏移边缘光出现在角色左边,向右偏移则出现在角色右边)

代码实现(部分)

首先我们需要一个脚本开启摄像机的深度模式,将该脚本挂于摄像机下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GetDepth : MonoBehaviour
{
    private Camera currentCamera = null;
    void Awake()
    {
        currentCamera = GetComponent<Camera>();
    }
    void Start()
    {
        currentCamera.depthTextureMode = DepthTextureMode.Depth;
    }

    void Update()
    {
        //currentCamera.depthTextureMode = DepthTextureMode.Depth;
    }
}

 此外,有了采样的思路代码就很好实现了(CG语言)

float2 offectSamplePos = screenParams01-float2(_RimOffect/input.clipW,0);//向右采样则是+
float offcetDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, offectSamplePos);
float trueDepth   = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenParams01);//等价于tex2D(_CameraDepthTexture, screenParams01);
float linear01EyeOffectDepth = Linear01Depth(offcetDepth);
float linear01EyeTrueDepth = Linear01Depth(trueDepth);
float depthDiffer = linear01EyeOffectDepth-linear01EyeTrueDepth;
float rimIntensity = step(_Threshold,depthDiffer);

效果

基于屏幕深度的边缘光效果图1

基于屏幕深度的边缘光效果图2 

可是此时,角色只有一边拥有边缘光。

我想让角色两边都有边缘光,该如何实现呢?

其实原理大同小异,只是这次,我们需要在角色两边都进行采样(uv坐标同时向两侧偏移)

采样过程

 代码实现(部分)

float2 offectSamplePosLeft = screenParams01-float2(_RimOffect/input.clipW,0);
float2 offectSamplePosRight = screenParams01+float2(_RimOffect/input.clipW,0);
float offcetDepthLeft = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, offectSamplePosLeft);
float offcetDepthRight = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, offectSamplePosRight);
float trueDepth  = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenParams01);
float linear01EyeOffectDepthLeft = Linear01Depth(offcetDepthLeft);
float linear01EyeOffectDepthRight = Linear01Depth(offcetDepthRight);
float linear01EyeTrueDepth = Linear01Depth(trueDepth);
float depthDifferLeft = linear01EyeOffectDepthLeft-linear01EyeTrueDepth;
float depthDifferRight = linear01EyeOffectDepthRight-linear01EyeTrueDepth;
float rimIntensityLeft = step(_Threshold,depthDifferLeft);
float rimIntensityRight = step(_Threshold,depthDifferRight);
float rimIntensity = max(rimIntensityLeft,rimIntensityRight);

效果

基于屏幕深度的边缘光效果图3

整合Shader

废话不多说了直接上代码(CG语言实现)

//This Shader just for study,not for project  
//LZX-VS2022-2024-10-22-002
Shader "Toon/Rim"
{
    Properties
    {
        [Header(Main)]
        _MainTex("Main Tex",2D) = "white"{}
         //Light Pass的参数还有其他pass的参数,因为我这个Shader只演示边缘光,所以只写了Rim Pass的参数
        [Header(Rim)]
        [Toggle] _SAMPING_BOTHSIDE("- SAMPING_BOTHSIDE ", float) = 0
        _RimOffect("RimOffect",range(0,1)) = 0.5
        _Threshold("RimThreshold",range(-1,1)) = 0.5
        _RimLightStrength("Rim Light Strength",Range(1,10)) = 2

    }
    SubShader
    {
         



        Pass//Lighting Pass
        {
        //正常情况下Light Pass不可能这么简单,这个Light Pass只是为了让角色衣服正常显示
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            sampler2D _MainTex;
            
            struct c2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float2 uv:TEXCOORD0;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:NORMAL;
                float2 uv:TEXCOORD;
                float3 objViewDir:COLOR1;
                float3 normal:NORMAL2;
                float clipW:TEXCOORD1;
            };
            v2f vert(c2v input)
            {
                v2f output;
                output.pos = UnityObjectToClipPos(input.vertex);
                output.worldNormal = normalize( mul((float3x3)unity_ObjectToWorld,input.normal) );
                output.uv = input.uv;


                float3 ObjViewDir = normalize(ObjSpaceViewDir(input.vertex));
                output.objViewDir = ObjViewDir;
                output.normal = normalize(input.normal);
                output.clipW = output.pos.w;
              
                return output;
            }
            fixed4 frag(in v2f input) : SV_Target
            {
               return tex2D(_MainTex, input.uv);
				
            }
            ENDCG
        }

         Pass//Rim Light Pass
        {
	    Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True"}
            ZWrite Off
           
            Blend SrcAlpha OneMinusSrcAlpha
            


            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature _SAMPING_BOTHSIDE_ON
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            sampler2D _MainTex;
            float _RimOffect;
	    float _Threshold;
            sampler2D _CameraDepthTexture; 
            float _RimLightStrength;

             struct c2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float2 uv:TEXCOORD0;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:NORMAL;
                float2 uv:TEXCOORD;
                float3 objViewDir:COLOR1;
                float3 normal:NORMAL2;
                float clipW:TEXCOORD1;
            };

            

            v2f vert(c2v input)
            {
                v2f output;
                output.pos = UnityObjectToClipPos(input.vertex);
                output.worldNormal = normalize( mul((float3x3)unity_ObjectToWorld,input.normal) );
                output.uv = input.uv;


                float3 ObjViewDir = normalize(ObjSpaceViewDir(input.vertex));
                output.objViewDir = ObjViewDir;
                output.normal = normalize(input.normal);
                output.clipW = output.pos.w;
              
                return output;
            }

            fixed4 frag(in v2f input) : SV_Target
            {
                fixed3 originCol = tex2D(_MainTex, input.uv).rgb;
		float2 screenParams01 = float2(input.pos.x/_ScreenParams.x,input.pos.y/_ScreenParams.y);
                #ifdef _SAMPING_BOTHSIDE_ON
                //两边都有边缘光
                float2 offectSamplePosLeft = screenParams01-float2(_RimOffect/input.clipW,0);
                float2 offectSamplePosRight = screenParams01+float2(_RimOffect/input.clipW,0);
                float offcetDepthLeft = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, offectSamplePosLeft);
		float offcetDepthRight = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, offectSamplePosRight);
                float trueDepth  = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenParams01);
                float linear01EyeOffectDepthLeft = Linear01Depth(offcetDepthLeft);
		float linear01EyeOffectDepthRight = Linear01Depth(offcetDepthRight);
                float linear01EyeTrueDepth = Linear01Depth(trueDepth);
                float depthDifferLeft = linear01EyeOffectDepthLeft-linear01EyeTrueDepth;
		float depthDifferRight = linear01EyeOffectDepthRight-linear01EyeTrueDepth;
                float rimIntensityLeft = step(_Threshold,depthDifferLeft);
                float rimIntensityRight = step(_Threshold,depthDifferRight);
                float rimIntensity = max(rimIntensityLeft,rimIntensityRight);
                #else
                //只有一边有边缘光
                float2 offectSamplePos = screenParams01-float2(_RimOffect/input.clipW,0);
                float offcetDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, offectSamplePos);
                float trueDepth   = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenParams01);
                float linear01EyeOffectDepth = Linear01Depth(offcetDepth);
                float linear01EyeTrueDepth = Linear01Depth(trueDepth);
                float depthDiffer = linear01EyeOffectDepth-linear01EyeTrueDepth;
                float rimIntensity = step(_Threshold,depthDiffer);
                #endif
                return fixed4(originCol * _RimLightStrength,rimIntensity);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

补充

实际上,这种基于屏幕深度的边缘光/描边普遍存在一个bug,那就是当角色处于屏幕边缘时,描边可能断开

Bug

游戏《绝区零》中出现的Bug 

 这是由于在屏幕空间中,角色模型的三角面被切割导致的

不过整体来说,无伤大雅

标签:深度图,图片说明,float,Shader,边缘,CameraDepthTexture,output,input,float2
From: https://blog.csdn.net/m0_49904624/article/details/143157194

相关文章

  • Shader内容释义
    //Shader名称:(Hidden/)+UniversalRenderPipeline/+(路径)/+功能名称(首字母大写驼峰式)Shader"UniversalRenderPipeline/CodingSpecification"//Shader"Hidden/UniversalRenderPipeline/CodingSpecification"{Properties{ //材质属性:_+首字母大写驼......
  • 36_初识搜索引擎_分页搜索以及deep paging性能问题深度图解揭秘
    课程大纲1、讲解如何使用es进行分页搜索的语法size,fromGET/_search?size=10GET/_search?size=10&from=0GET/_search?size=10&from=20分页的上机实验GET/test_index/test_type/_search"hits":{"total":9,"max_score":1,我们假设将这9条数据分成3页,每一页是3条数......
  • 29_分布式文档系统_深度图解剖析document数据路由原理
    (1)document路由到shard上是什么意思?(2)路由算法:shard=hash(routing)%number_of_primary_shards举个例子,一个index有3个primaryshard,P0,P1,P2每次增删改查一个document的时候,都会带过来一个routingnumber,默认就是这个document的_id(可能是手动指定,也可能是自动生成)routing=_i......
  • Shader预热生成的内容会被RUUA卸载吗
    1)Shader预热生成的内容会被RUUA卸载吗2)纯WebGL可以实现微信小游戏提供的WASM分包功能吗3)如何为单个实例添加命中计数4)如何从蓝图中的for循环中获得所有Index这是第402篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地掌握和......
  • 3d可视化图片:通过原图和深度图实现
    1、depthy在线体验demo:https://depthy.stamina.pl/#/也可以docker安装上面服务:dockerrun--rm-t-i-p9000:9000ndahlquist/depthyhttp://localhost:90001)首先传原图2)再传对应深度图3)效果</ifra......
  • three.js shader 入门 红旗飘动效果
    预览效果1、懒人直接上代码,全部代码效果import*asTHREEfrom"https://esm.sh/three";import{OrbitControls}from"https://esm.sh/three/examples/jsm/controls/OrbitControls";consttextureLoader=newTHREE.TextureLoader()letcontrols;letscene:TH......
  • Shader Graph自定义渐变色节点Gradiant
    ShaderGraph自定义渐变色节点GradiantUnity自带Shader中的Gradiant不能暴露在外部使用定义CustomFunction来制作暴露给外部的GradiantShaderGraph节点图CustomFunction代码if(inputValue<location1){outFloat=color1;}else......
  • ThreeJS Shader的效果样例光影墙、扩散面(四)
    一、实现一个光影墙1. 根据自定义坐标点,输出一个光影墙/***添加光影墙*/functionaddLightWall(){constgeometry=newTHREE.BufferGeometry();constvertices=newFloat32Array([5,0,2,3,0,5,-2,0,5,-4,0,2,-4,......
  • shader 案例学习笔记之绘制圆
    环境搭建:参考glslvscode环境搭建先上代码#ifdefGL_ESprecisionmediumpfloat;#endifuniformvec2u_resolution;voidmain(){vec2st=gl_FragCoord.xy/u_resolution.xy;st-=0.5;st.x*=u_resolution.x/u_resolution.y;floatr=length(st);......
  • Amplify Shader Editor学习
    半透明边缘光效果原理整个效果分为两部分,一个是半透明效果另一个是边缘发光,对于第一个效果来说我们只需要使用透明度混合的方法就可以办到,第二个效果的关键在于怎么辨别边缘?我们可以用法线向量点乘视线向量来辨别.\[\vec{a}*\vec{b}=|a|*|b|*cos\theta,所以可以通过......