Unity学习shader笔记[二十]距离扫描

(102条消息) Unity学习shader笔记[二十]距离扫描_小小~~的博客-CSDN博客

从最开始学习shader的时候就想着会不会有一天能够接触了解这种效果的原理,当时网上看到类似的博客内心是惊喜的,只是看了原理之后真的感觉很难。第一次接触这种效果是在《虐杀原形2》游戏中,我玩了n遍,这次的博客记录主要是为了致敬这部游戏。

 

深度距离主体思想是得到屏幕成像的每个像素点的世界坐标,比较世界坐标与扫描中心点的距离实现扫描效果。 主要在怎么得到世界坐标这个问题上,原理有两种:

通过顶点的空间变换过程的逆过程实现,根据渲染流水,空间变换过程是从模型坐标变换到屏幕坐标的过程,屏幕呈现出的图像是在屏幕坐标下的,需要进行这个过程的逆运算来计算出每个像素点的世界坐标。这个运算在片元着色器进行,效率较低,好处是摄像机无论是正交视角还是透视视角都能正常运行效果。
屏幕传回来的图像是由两个三角形拼接成的矩形,总共四个顶点,计算出摄像机位置对摄像机远*面四个顶点的方向向量ABCD,则屏幕矩形的四个顶点会一一在这四个方向向量的某个轨迹上, 如图所示:

在将屏幕图像传入材质shader进行处理的时候,流水线异常强大的插值能力会根据这四个方向向量插值计算出摄像机到每个像素点的方向向量,获得了每个像素的方向向量后,需要将归一化的深度值反归一化,得到像素点在世界空间下到摄像机的距离,然后这个距离乘以方向向量最后加上摄像机的坐标,得到像素点的世界坐标,这个过程虽然在片元着色器进行,但是不涉及矩阵运算,效率较好,坏处是摄像机只能是透视视角,因为正交视角下视锥体是一个长方体,不像透视视图下是个最终集于一点的去头锥体,所以物体的成像是*裁*面垂直映射到屏幕的,想象上图变成一个长方体,而屏幕矩形是长方体的一个截面,这时就不能使用相机到像素的向量了,正交视图的成像矩阵不同导致插值计算出的方向向量不正确,计算出的像素世界坐标也是不正确的。

这里先给出第二种方案的实现代码,第一种的有空有心情再看看:

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

[ExecuteInEditMode]
public class PosScanEffect : MonoBehaviour
{
public Material ScanMat;

public float ScanSpeed = 20;

public float scanTimer = 0;

private Camera scanCam;

private Vector3 ScanPoint = Vector3.zero;

 


void Awake()
{
scanCam = GetComponent<Camera>();

ScanMat.SetFloat("_CamFar", scanCam.farClipPlane);

//这里设置国深度贴图模式之后 shader里面就能直接访问到 _CameraDepthNormalsTexture
scanCam.depthTextureMode |= DepthTextureMode.Depth;
scanCam.depthTextureMode |= DepthTextureMode.DepthNormals;
}

private void Update()
{

 


float aspect = scanCam.aspect;

float farPlaneDistance = scanCam.farClipPlane;

//fieldOfView是摄像机视角的竖直角度
//halfHeight是摄像机远截*面的一半的高度
float halfHeight = Mathf.Tan(scanCam.fieldOfView / 2 * Mathf.Deg2Rad) * farPlaneDistance;

//midup是摄像机朝上并且摄像机远截*面的一半的高度的向量
Vector3 midup = halfHeight * scanCam.transform.up;

//midright是摄像机朝右并且长度为摄像机远截*面的一半的宽度的向量
Vector3 midright = halfHeight * aspect * scanCam.transform.right ;

//farPlaneMid是摄像机到远*面的距离向量
Vector3 farPlaneMid = scanCam.transform.forward * farPlaneDistance;

//根据向量相减运算法则 bottomLeft是摄像机到其远*面左下角的向量
Vector3 bottomLeft = farPlaneMid - midup - midright;

//根据向量加减运算法则 bottomLeft是摄像机到其远*面右下角的向量
Vector3 bottomRight = farPlaneMid - midup + midright;

//upLeft是摄像机到其远*面左上角的向量
Vector3 upLeft = farPlaneMid + midup - midright;

//upRight是摄像机到其远*面右上角的向量
Vector3 upRight = farPlaneMid + midup + midright;


Matrix4x4 frustumCorner = new Matrix4x4();
//放入四乘四矩阵

frustumCorner.SetRow(0, bottomLeft);
frustumCorner.SetRow(1, bottomRight);
frustumCorner.SetRow(2, upRight);
frustumCorner.SetRow(3, upLeft);
//传入材质的shader的_FrustumCorner变量
ScanMat.SetMatrix("_FrustumCorner", frustumCorner);


RaycastHit hit;


//射线检测鼠标点击
if (Input.GetMouseButton(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

if(Physics.Raycast(ray,out hit)){
scanTimer = 0;
ScanPoint = hit.point;
//点击到的地面的世界位置传入材质的shader
ScanMat.SetVector("_ScanCenter", ScanPoint);
}
}

scanTimer += Time.deltaTime;
ScanMat.SetFloat("_ScanRange", scanTimer*ScanSpeed);
//传入从观察空间到世界空间的转换矩阵
ScanMat.SetMatrix("_CamToWorld", scanCam.cameraToWorldMatrix);
}

private void OnRenderImage(RenderTexture source, RenderTexture destination)
{


Graphics.Blit(source, destination, ScanMat);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
Shader "Custom/PointScanShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_ScanRange("ScanRange",float) = 0
_ScanWidth("ScanWidth",float) = 0
_ScanBgColor("ScanBgColor",color)=(1,1,1,1)
_MeshLineWidth("MeshLineWidth",Range(0, 1))=0.3
_MeshWidth("MeshWidth",float)=1
_Smoothness("SeamBlending",Range(0,0.5))=0.25
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2;
float4 vertex : SV_POSITION;
};


float4x4 _FrustumCorner;

v2f vert (appdata v)
{
v2f o;
//将顶点从模型空间转换到剪裁空间
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.uv_depth = v.uv;

int rayIndex;
//根据顶点所属的uv在哪一部分来获得自身所属的向量

//uv在左下角
if(v.uv.x<0.5&&v.uv.y<0.5){
//左下角向量
rayIndex = 0;
//uv在右下角
}else if(v.uv.x>0.5&&v.uv.y<0.5){
//右下角向量
rayIndex = 1;
//uv在右上角
}else if(v.uv.x>0.5&&v.uv.y>0.5){
//右上角向量
rayIndex = 2;
//uv在左上角
}else{
//左上角向量
rayIndex = 3;
}

o.interpolatedRay = _FrustumCorner[rayIndex];

return o;
}

sampler2D _MainTex;
float _ScanRange;
float _ScanWidth;
float3 _ScanCenter;
fixed4 _ScanBgColor;

//扫描的正方形网格线的粗细百分比 值在0到1之间 理想的是在0.8以下
float _MeshLineWidth;

//扫描的正方形的大小 在0到50之间效果比较明显
float _MeshWidth;


float4x4 _CamToWorld;
fixed _Smoothness;

sampler2D_float _CameraDepthTexture;
sampler2D _CameraDepthNormalsTexture;

fixed4 frag (v2f i) : SV_Target
{
float tempDepth;

half3 normal;

//采样深度法线贴图 获得法线向量
DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), tempDepth, normal);

//将法线从观察空间转换到世界空间
normal = mul( (float3x3)_CamToWorld, normal);

//
normal = normalize(max(0, (abs(normal) - _Smoothness)));

//采样摄像机传入的屏幕图像的颜色
fixed4 col = tex2D(_MainTex, i.uv);


//根据摄像机的uv采样深度贴图
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);

//将深度限制在零一范围内
float linearDepth = Linear01Depth(depth);
// float linearDepth = (depth);

// _WorldSpaceCameraPos是UnityCG.cginc内置的宏定义变量
// 此时的interpolatedRay是经过顶点到片元的插值的
//
float3 pixelWorldPos =_WorldSpaceCameraPos + linearDepth * i.interpolatedRay;


//像素的世界坐标到扫描中心坐标的距离
float pixelDistance = distance(pixelWorldPos , _ScanCenter);

//从中心到片元世界位置的方向
float3 pixelDir = pixelWorldPos - _ScanCenter;

// 这部分不用看
// //取模运算,分两步,和取余基本一样
// // c = floor([a/b]); //代表a能被b整除成几份
// // r = a - c*b. //减去能被整除的就是余数了
// float3 modulo = pixelWorldPos - _MeshWidth * floor(pixelWorldPos / _MeshWidth);
//
// //余数部分占_MeshWidth的百分比 根据余数的概念 值在0到1之间
// modulo = modulo / _MeshWidth;




//离得越远 深度越接*1 天空盒的深度是1 要去除天空盒的扩散 所以 linearDepth<1

if(_ScanRange - pixelDistance > 0 && //表示到扫描中心的距离小于扫描范围的像素
_ScanRange - pixelDistance <_ScanWidth &&
linearDepth<1){

//越接*_ScanRange 则应该越显示扫描的颜色 越远离则越应该不显示 所以1减
fixed scanPercent = 1 - (_ScanRange - pixelDistance) / _ScanWidth;

col = lerp(col, _ScanBgColor, scanPercent);
}

return col;
}
ENDCG
}
}
}


//参考网站 https://baike.baidu.com/item/%E5%8F%96%E6%A8%A1%E8%BF%90%E7%AE%97/10739384?fr=aladdin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
最后结果如下所示:

 


Demo在这, 上图用到了Linear0Depth这个函数,原因参考 LinearEyeDepth和Linear01Depth 。
————————————————
版权声明:本文为CSDN博主「小小~~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43149049/article/details/112547009

posted on 2021-11-04 09:26  guanxi0808  阅读(510)  评论(0)    收藏  举报

导航