高级纹理

CubeMap

类似于天空盒,正方体的六个面分别渲染

实现方式

手动拖拽6张无缝贴图

在Assets中中新建一个Cubemap,位置在Legacy/CubeMap中,也可以自行搜索,选择图片之前要先选择清晰度,之后再改无效

程序化生成

代码

实际上该操作是以指定物体为中心,将周围常见渲染到cubemap上

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
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class RenderCubeMap : ScriptableWizard
{
public Transform renderPos;

public Cubemap cubemap;

[MenuItem("Tools/3D Render Cubemap")]
public static void CreateCubeMap()
{
ScriptableWizard.DisplayWizard<RenderCubeMap>("Render Cubemap", "Create");
}

private void OnWizardCreate()
{
GameObject go = new GameObject("Cumapcam");
Camera cam = go.AddComponent<Camera>();
go.transform.position = renderPos.position;
cam.RenderToCubemap(cubemap);
DestroyImmediate(go);
}

private void OnWizardUpdate()
{
helpString = "Render cubemap";
isValid = (cubemap != null) && (renderPos != null);
}
}

在Assets文件中创建

image-20241113091201901 image-20241113091422626 image-20241113091624120

反射探针

手动在场景中创建反射探针,在操作窗口点击Bake生成,会以当前翻身探针为中心生成Cubemap

image-20241113093915943

在场景的世界坐标000默认存在一个反射探针,同样可以用于Cubemap创建,使用方式是lightmap中的生成

反射

使用的依据是物理中的反射过程

reflect函数字面意思虽然是反射,但是反射的不一定是光线,任何向量都能作反射向量,texCUBE用视角方向的反方向求取反射向量来采样cubemap的像素,是比较常用的例子

补充

Unity的视角向量是指向相机本身的,灯光的向量也是从模型点指向自身的,在实际使用过程中,指向自身的灯光向量才能计算和法线的cos值

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
Shader "Unlit/19"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_CubeMap("CubeMap",Cube) = "_Skybox"{}
_ReflectionColor("ReflectionColor",Color) = (1,1,1,1)
_ReflectionAmount("ReflectionAmount",Range(0,1)) = 0.1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"


struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float3 worldPos :TEXCOORD2;
float3 worldNormal:TEXCOORD3;
float3 worldView : TEXCOORD4;
float3 worldRef : TEXCOORD5;
};

sampler2D _MainTex;
float4 _MainTex_ST;

fixed4 _ReflectionColor;
half _ReflectionAmount;
samplerCUBE _CubeMap;

v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldView = UnityWorldSpaceViewDir(o.worldPos);
o.worldRef = reflect(-o.worldView,o.worldNormal);

UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 viewDir = normalize(i.worldView);

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
fixed3 diffuse = col.rgb * _LightColor0.rgb * (dot(worldLightDir,worldNormal) * 0.5 + 0.5);
fixed3 reflection = texCUBE(_CubeMap,i.worldRef).rgb * _ReflectionColor;
fixed3 resColor= ambient + lerp(diffuse,reflection,_ReflectionAmount);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, resColor);
return fixed4(resColor,1);
}
ENDCG
}
}
}
image-20241113104852230

折射

基本过程和反射类似,具体是在顶点着色器中的函数使用不同,反射是reflect,则是则是refract

参数讲解,第一个是入射光线,第二是世界法线,第三个是折射率

顶点着色器中新添加内容

1
o.worldRefra = refract(-normalize(o.worldView),normalize(o.worldNormal),_RefractRotio);

在片元着色器中的过程没有什么区别

菲涅尔反射

不同角度去看一个反射面,折射和反射光的占比不同

水,垂直于平面去看就是折射大,近似平行于水面就是反射大

菲涅尔反射式一种描述反射和折射关系的公式

公式

Schlick菲涅尔近似等式:

image-20241113110919934

Empricial菲涅尔近似等式:

image-20241113110936437

使用最多的是Schick近似公式

应用

1
2
//视角方向,世界法线,菲涅尔系数
fixed3 fresnel = _FresnelScale + (1-_FresnelScale) * pow(1-dot(viewDir,worldNormal),5);

镜面效果

利用RenderTexture来进行实现

常用途径

  • 小地图
  • 镜子

RenderTexture

将相机拍摄到的图输出到RenderTexture上,需要手动在Assets中进行创建,可直接作为一个shader的TEX

使用RenderTextrue进行实际上是利用延迟渲染的输出,具体特性自行搜索

对一个镜子来说,摄像机的图片左右会有翻转,需要手动改变uv.x的方向,在顶点着色器中进行如下配置就行,RenderTexture可以直接被MainTex使用

1
o.uv.x = 1 - o.uv.x;

抓屏

注意

之间的版本不想使用,因为有带宽的消耗,现在能用,但是要考虑性能,比如每一个shader都有一个抓屏就有点浪费,一个Shader声明后,别的shader直接 use该通道即可,否则即视为进行了多次抓屏,用法上抓屏命令中的名字相同即可,

使用顺序

多个Shader中使用同一个抓屏,就会使用渲染队列考靠前的

常用途径:

1.截取全屏作为截图储存 (常用)。
2.截取全屏,模糊处理当作背景。
3.接入屏幕中某些指定的画面。

抓屏命令:

GrabPass{“Name”}

释义: 定义抓屏通道 GrabPass{“_GrabPassTexture”}

多Shader使用同一个GrabPass也是这样

声明抓屏通道名称:

GrabPassTexture 表示抓屏通道的名称,如果声明了抓屏通道的名称,只需在shader属性声明区声明一下,Unity会自动把抓屏的图形填充到我们声明的GrabPassTexture中,我们可直接通过_GrabPassTexture直接使用该贴图。

不声明抓屏通道名称:

如果不填写抓屏的名称,那么Unity就会默认使用_GrabPassTexture进行保存,我们只需要在Shader属性声明区声明_GrabPassTexture属性,Unity会自动把抓屏完成的贴图填充进去

示例代码

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
Shader "Unlit/GrabPss"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
//设置渲染顺序为最后渲染,这样抓到的屏幕图形则是在所有物体渲染完成后的图像
Tags { "RenderType"="Opaque" "Queue"="Overlay"}
LOD 100
//定义抓屏通道 {"_GrabPassTexture"} 表示抓屏的名称,如果声明的抓屏的名称,只需在shader中在声明一下,Unity会自动把抓屏的图形填充到我们声明的_GradaPss中
//如果不填写抓屏的名称,那么Unity就会默认使用_GrabTexture进行保存
GrabPass{"_GrabPassTexture"}

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag


#include "UnityCG.cginc"

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

struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;

//抓屏Texture属性
sampler2D _GrabPassTexture;
fixed4 _GrabPassTexture_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
//因为采样得到的图片是反的所以我们这里翻转X轴
o.uv.x=1-o.uv.x;
o.uv = TRANSFORM_TEX(v.uv, _GrabPassTexture);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_GrabPassTexture, i.uv);
return col;
}
ENDCG
}
}
}


实现案例

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
Shader "Unlit/23"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_Diffuse("Color",Color) = (1,1,1,1)
_BumpMap("Normal Map",2D) = "white"{}
_BumpScale("Bump Scale", float) = 1
_Cubemap("CubeMap",Cube) = "_Skybox"{}
_Distortion("Distortion",Range(0,100)) = 10
_RefractAmount("RefractAmount",Range(0,1)) = 1
}

SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Transparent+100"}
LOD 100

GrabPass{"GrabPass"}

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile __ SNOW_ON
#include "UnityCG.cginc"
#include "Lighting.cginc"

struct v2f
{
float4 vertex : SV_POSITION;
float4 uv :TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 :TEXCOORD2;
float4 TtoW2 :TEXCOORD3;
float4 scrPos: TEXCOORD4;
};

sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Diffuse;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
sampler2D GrabPass;
float4 GrabPass_TexelSize;
float _Distortion;
samplerCUBE _Cubemap;
float _RefractAmount;

v2f vert(appdata_tan v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex);
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * 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
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT;

fixed4 albedo = tex2D(_MainTex, i.uv);

float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);

fixed3 lightDir = UnityWorldSpaceLightDir(worldPos);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

//求法线
fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
fixed3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
fixed3 worldNormal = normalize(float3(dot(i.TtoW0.xyz, tangentNormal), dot(i.TtoW1.xyz, tangentNormal),dot(i.TtoW2.xyz,tangentNormal)));

//采样抓屏贴图
float2 offset = tangentNormal.xy * _Distortion * GrabPass_TexelSize.xy;

//给扰动值offset乘上屏幕空间的深度z,在屏幕空间越深的地方,扰动越明显
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;

//i.scrPos,是使用
//o.vertex = UnityObjectToClipPos(v.vertex);
//o.scrPos = ComputeGrabScreenPos(o.vertex);
//这两个方法取得的,这里的屏幕空间坐标没有进行齐次除法,并不是真正的屏幕空间
//使用xy/w,就能得到
fixed3 refrCol = tex2D(GrabPass,i.scrPos.xy/i.scrPos.w).rgb;

fixed3 reflCol = texCUBE(_Cubemap, reflect(-viewDir, worldNormal)).rgb * albedo;


fixed3 color = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
return fixed4(color,1);
}
ENDCG
}
}
}
image-20241113162911745

抓屏中用到的特殊属性

1
2
sampler2D GrabPass; //GrabPassName配合的
float4 GrabPass_TexelSize;
  • 这不是缩放值,而是一个包含纹理尺寸和纹素大小(texel size)的向量。具体来说,GrabPass_TexelSizexy分量通常表示纹理的一个纹素(即纹理中的一个像素或纹理单元)对应的屏幕空间尺寸(以世界单位或屏幕空间的某种度量表示,这取决于Unity的内部实现和渲染设置)。zw分量可能表示纹理的总尺寸(宽度和高度)的倒数,但具体含义可能因Unity的版本和渲染管道而异。
  • 重要的是要理解GrabPass_TexelSizexy分量提供了从纹理坐标到屏幕空间坐标(或反之)进行转换的比例因子。这对于实现精确的屏幕空间效果至关重要,因为它允许你根据纹理的分辨率和屏幕的分辨率来计算偏移量或进行其他相关的数学计算。

Command_Buffers

Unity默认渲染管线提供的C#接口,在每个渲染顺序完成后给吹一张图片让我们进行处理

补充

程序纹理

Unity2017版本以及之前的shader可视化开发可以使用插件,shader forge,但是shaderfoege对默认渲染管线的支持不是很好,同时shaderforge有关的纹理叫做程序化纹理,随着版本的更新,暂时不需要学习,当前Unity提供的可视化shader工具叫做 shader graph

深度图采样

高度物、水效果

提示

某些手机端使用samlper2D会造成贴图精度不够,这个时候使用sampler2D_float来提升精度