UnityShader-高级纹理

高级纹理

立方体纹理

立方体纹理(Cubemap)环境映射的一种实现形式,可以用于模拟物体周围的环境,或者模拟金属反射周围的环境

原理:

立方体纹理包含6个图像,在进行纹理采样时,计算从立方体中心出发的矢量和立方体的交点,然后对该交点进行像素采样

cubemap_sample.png-20.1kB

优点:

实现快速,效果良好

缺点:

当场景中引入新的物体、光源,或者物体本身需要移动时,立方体纹理也需要重新生成

不能反射使用了该立方体纹理的物体本身,难以处理金属的多次反射

天空盒子(Skybox)

用于模拟背景或者天空,整个场景被包围在一个立方体内

实现:

  1. 新建材质,在UnityShader下拉菜单中选择Skybox/6 Sided
  2. 使用6张纹理对材质进行赋值
  3. 设置纹理WrapMode为Clamp,防止出现接缝处不匹配现象
  4. 调整属性:
    • Tint Color:控制该材质的整体颜色
    • Exposure:调整天空盒子的亮度
    • Rotation:调整天空盒子沿+y轴方向的旋转角度
  5. 在Window-》Rendering-》Lighting中,将该材质赋值给Skybox选项

也可以给摄像机设置单独的天空盒子来覆盖默认的,只需要添加Skybox组件即可

创建用于环境映射的立方体纹理

  1. 直接由特殊布局的纹理创建。该纹理类似于立方体展开图的交叉布局、全景布局等。该方法可以对纹理数据进行压缩,也支持边缘修正、光滑反射等
  2. 手动创建Cubemap资源,再把6张图赋值给它
  3. 由脚本生成

使用方法1创建立方体纹理:

  1. 指定环境映射的位置

  2. 使用Camera.RenderToCubemap函数创建立方体纹理,相关代码如下,需要放置在Editor文件夹下

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

    public class RenderCubemapWizard : ScriptableWizard {

    public Transform renderFromPosition;
    public Cubemap cubemap;

    void OnWizardUpdate () {
    helpString = "Select transform to render from and cubemap to render into";
    isValid = (renderFromPosition != null) && (cubemap != null);
    }

    void OnWizardCreate () {
    // create temporary camera for rendering
    GameObject go = new GameObject( "CubemapCamera");
    go.AddComponent<Camera>();
    // place it on the object
    go.transform.position = renderFromPosition.position;
    // render into cubemap
    go.GetComponent<Camera>().RenderToCubemap(cubemap);

    // destroy temporary camera
    DestroyImmediate( go );
    }

    [MenuItem("GameObject/Render into Cubemap")]
    static void RenderCubemap () {
    ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
    "Render cubemap", "Render!");
    }
    }
  3. 选择Create-》Legacy-》Cubemap创建立方体纹理,在面板中勾选Readable选项

  4. 使用Wizard(刚刚编写的向导脚本)对立方体纹理添加具体图像

  5. 调整Face Size选项,值越大分辨率越大,但是占用内存也越多

反射

原理:通过入射光线的方向和表面法线的方向计算反射方向,再利用反射方向对立方体进行纹理采样

实现代码如下:

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
Shader "Reflection" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
// 控制漫反射颜色
_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1)
// 控制材质的反射程度
_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1
_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

Pass {
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"
#include "AutoLight.cginc"

fixed4 _Color;
fixed4 _ReflectColor;
fixed _ReflectAmount;
samplerCUBE _Cubemap;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefl : TEXCOORD3;
SHADOW_COORDS(4)
};

v2f vert(a2v v) {
v2f o;

o.pos = UnityObjectToClipPos(v.vertex);

o.worldNormal = UnityObjectToWorldNormal(v.normal);

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);

// Compute the reflect dir in world space
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);

TRANSFER_SHADOW(o);

return o;
}

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

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));

// Use the reflect dir in world space to access the cubemap
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

// Mix the diffuse color with the reflected color
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;

return fixed4(color, 1.0);
}

ENDCG
}
}
FallBack "Reflective/VertexLit"
}

折射

原理:当光线从一种介质斜射入另一种介质时,传播方向一般会发生改变

使用**斯涅尔定律(Snell’s Law)**来计算反射角:
$$
n_1sin\theta_{1} = n_2sin\theta_{2}
$$
其中n1,n2为折射率(index of refraction)。图形学中通常只模拟一次折射

实现代码如下:

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
Shader "Refraction" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_RefractColor ("Refraction Color", Color) = (1, 1, 1, 1)
_RefractAmount ("Refraction Amount", Range(0, 1)) = 1
// 不同介质的透射比
_RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5
_Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

Pass {
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"
#include "AutoLight.cginc"

fixed4 _Color;
fixed4 _RefractColor;
float _RefractAmount;
fixed _RefractRatio;
samplerCUBE _Cubemap;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefr : TEXCOORD3;
SHADOW_COORDS(4)
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.worldNormal = UnityObjectToWorldNormal(v.normal);

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);

// Compute the refract dir in world space
// 参数一为入射光线的方向,必须是归一化之后的矢量
// 参数二是表面法线,需要归一化
// 参数三是入射光线所在介质的折射率和折射光线所在介质的折射率之间的比值
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);

TRANSFER_SHADOW(o);

return o;
}

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

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));

// Use the refract dir in world space to access the cubemap
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

// Mix the diffuse color with the refract color
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;

return fixed4(color, 1.0);
}

ENDCG
}
}
FallBack "Reflective/VertexLit"
}

菲涅耳反射(Fresnel reflection)

原理:

当光线照射到物体表面时,一部分发生反射,一部分进入物体内部,发生折射或者散射。被反射的光和入射光之间存在一定的比率关系,该比率可以通过菲涅耳等式计算。

Schlick菲涅耳近似等式:
$$
F_{schlick}(v, n) = F_0 + (1 - F_0)(1 - v \cdot n)^{5}
$$
其中,$F_0$ 是一个反射系数,用于控制菲涅耳反射的强度,$v$ 是视角方向,$n$ 是表面法线。

Empricial菲涅耳近似等式:
$$
F_{Empricial}(v, n) = max(0, min(1, bias + scale \times (1 - v \cdot n)^{power}))
$$
其中,bias, scale 和 power是控制项

实现代码如下:

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
Shader "Fresnel" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_FresnelScale ("Fresnel Scale", Range(0, 1)) = 0.5
_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

Pass {
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"
#include "AutoLight.cginc"

fixed4 _Color;
fixed _FresnelScale;
samplerCUBE _Cubemap;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefl : TEXCOORD3;
SHADOW_COORDS(4)
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.worldNormal = UnityObjectToWorldNormal(v.normal);

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);

o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);

TRANSFER_SHADOW(o);

return o;
}

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

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;

fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);

fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));

fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;

return fixed4(color, 1.0);
}

ENDCG
}
}
FallBack "Reflective/VertexLit"
}

渲染纹理

渲染目标纹理(RTT):现代GPU允许把整个三维场景渲染到一个中间缓冲中

多重渲染目标(MRT):GPU允许把场景同时渲染到多个渲染目标纹理中,不再需要为每个渲染目标纹理单独渲染完整的场景

渲染纹理(Render Texture):Unity为渲染目标定义了一种专门的纹理类型,通常有两种方式:

  • 在Project目录下创建一个渲染纹理,然后把某个摄像机的渲染目标设置成该渲染纹理,这样摄像机的渲染结果就会实时更新到渲染纹理中,而不会显示在屏幕上。还可以选择渲染纹理分辨率、滤波模式等属性
  • 在屏幕后处理使用时使用Grab Pass命令或者OnRenderImage函数获取当前屏幕图像,Unity将图像放到一张和屏幕分辨率等同的渲染纹理中

镜子效果

实现原理:使用一个渲染纹理作为输入属性,并把该渲染纹理在水平方向上翻转后直接显示到物体上

在Project视图中创建渲染纹理(Create-》Render Texture)

为了得到从镜子出发观察到的图像,需要创建一个摄像机,并调整它的位置、裁剪平面、视角等,使得它显示的图像是我们希望的图像。再将渲染纹理(Mirror Texture)附加到该摄像机上

render_texture.png-113.3kB

渲染纹理的分辨率可能影响到画面效果,此时可以调整为更高的分辨率或者进行抗锯齿采样,但同时也会损失性能

相关代码如下:

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
Shader "Mirror" {
Properties {
// 对应由镜子摄像机渲染得到的纹理
_MainTex ("Main Tex", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

Pass {
CGPROGRAM

#pragma vertex vert
#pragma fragment frag

sampler2D _MainTex;

struct a2v {
float4 vertex : POSITION;
float3 texcoord : TEXCOORD0;
};

struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord;
// Mirror needs to filp x
o.uv.x = 1 - o.uv.x;

return o;
}

fixed4 frag(v2f i) : SV_Target {
return tex2D(_MainTex, i.uv);
}

ENDCG
}
}
FallBack Off
}

玻璃效果

原理:

  • 使用法线纹理来修改模型的法线信息
  • 通过Cubemap模拟玻璃的反射
  • 使用GrabPass抓取玻璃后的屏幕图像后,使用切线空间下的法线对屏幕纹理坐标偏移以模拟近似的折射效果

相关代码如下:

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
Shader "Glass Refraction" {
Properties {
// 玻璃的材质纹理,默认为白色
_MainTex ("Main Tex", 2D) = "white" {}
// 玻璃的法线纹理
_BumpMap ("Normal Map", 2D) = "bump" {}
// 用于模拟反射的环境纹理
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
// 控制模拟折射时图像的扭曲程度,该值越大,计算得到的偏移量越大,玻璃背后的物体形变程度越大
_Distortion ("Distortion", Range(0, 100)) = 10
// 控制折射程度,为0时只有反射效果,为1时只有折射效果
_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0
}
SubShader {
// We must be transparent, so other objects are drawn before this one.
// RenderType=Opaque: 在使用着色器替换时,该物体在需要时能被正确渲染,通常发生在需要得到摄像机的深度和法线纹理时
Tags { "Queue"="Transparent" "RenderType"="Opaque" }

// This pass grabs the screen behind the object into a texture.
// We can access the result in the next pass as _RefractionTex
// 抓取得到的屏幕图像将被存储在该纹理中
// 注意:
// 如果声明了纹理,则Unity只会执行一次屏幕图像的抓取,即在第一次使用含有该指令的材质中,这样能够节省性能
// 如果没有声明纹理(GrabPass {}),则Unity会在每一次运行该指令时进行屏幕抓取,即允许不同的物体具有不同的屏幕图像
// 这取决于物体当时的渲染队列、颜色缓冲区等要素
GrabPass { "_RefractionTex" }

Pass {
CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _Cubemap;
float _Distortion;
fixed _RefractAmount;
// 抓取得到的屏幕图像
sampler2D _RefractionTex;
// 上述纹理的纹素大小(1/width, 1/height)
float4 _RefractionTex_TexelSize;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord: TEXCOORD0;
};

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

v2f vert (a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.scrPos = ComputeGrabScreenPos(o.pos);

// 提取纹理坐标
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

// 计算从切线空间变换到世界空间的矩阵
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
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 {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

// Get the normal in tangent space
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));

// Compute the offset in tangent space
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
// 乘以i.srcPos.z可以让变形程度随着摄像机的远近发生变化
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
// 对srcPos进行透视除法得到真正的NDC
// 原因:
// 在顶点着色器中通过ComputeGrabScreenPos计算得到的坐标没有经过齐次除法,因为需要对xy进行正确插值
// 在经过插值后,我们手动进行透视除法,可以得到正确结果,该结果为NDC下坐标,此时可以被当作纹理坐标使用了
fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;

// Convert the normal to world space
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 reflDir = reflect(-worldViewDir, bump);
fixed4 texColor = tex2D(_MainTex, i.uv.xy);
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;

fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;

return fixed4(finalColor, 1);
}

ENDCG
}
}

FallBack "Diffuse"
}

渲染纹理和GrabPass的比较

GrabPass:

  • 在Shader中编写相关指令,使用简单
  • 效率上次于渲染纹理,特别是在移动设备上

渲染纹理:

  • 首先需要创建一个渲染纹理和摄像机,然后摄像机计算得到渲染纹理后将纹理交给shader
  • 可以自定义渲染纹理大小
  • 可以通过调整摄像机的渲染层来减少二次渲染的场景大小
  • 可以控制摄像机是否需要开启
  • 移动设备优先使用该方法

Unity的命令缓冲也能够得到类似于抓屏的效果,Unity - Manual: Extending the Built-in Render Pipeline with CommandBuffers (unity3d.com)

程序纹理

使用特定的算法来创建图案,可以更加自由地控制纹理外观和形状

使用代码生成波点纹理

生成脚本:

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

// 保证该脚本能够在编辑器模式下运行
[ExecuteInEditMode]
public class ProceduralTextureGeneration : MonoBehaviour {

public Material material = null;

#region Material properties
[SerializeField, SetProperty("textureWidth")]
private int m_textureWidth = 512;
public int textureWidth {
get {
return m_textureWidth;
}
set {
m_textureWidth = value;
_UpdateMaterial();
}
}

[SerializeField, SetProperty("backgroundColor")]
private Color m_backgroundColor = Color.white;
public Color backgroundColor {
get {
return m_backgroundColor;
}
set {
m_backgroundColor = value;
_UpdateMaterial();
}
}

[SerializeField, SetProperty("circleColor")]
private Color m_circleColor = Color.yellow;
public Color circleColor {
get {
return m_circleColor;
}
set {
m_circleColor = value;
_UpdateMaterial();
}
}

[SerializeField, SetProperty("blurFactor")]
private float m_blurFactor = 2.0f;
public float blurFactor {
get {
return m_blurFactor;
}
set {
m_blurFactor = value;
_UpdateMaterial();
}
}
#endregion

private Texture2D m_generatedTexture = null;

// Use this for initialization
void Start () {
if (material == null) {
Renderer renderer = gameObject.GetComponent<Renderer>();
if (renderer == null) {
Debug.LogWarning("Cannot find a renderer.");
return;
}

material = renderer.sharedMaterial;
}
_UpdateMaterial();
}

private void _UpdateMaterial() {
if (material != null) {
m_generatedTexture = _GenerateProceduralTexture();
material.SetTexture("_MainTex", m_generatedTexture);
}
}

private Color _MixColor(Color color0, Color color1, float mixFactor) {
Color mixColor = Color.white;
mixColor.r = Mathf.Lerp(color0.r, color1.r, mixFactor);
mixColor.g = Mathf.Lerp(color0.g, color1.g, mixFactor);
mixColor.b = Mathf.Lerp(color0.b, color1.b, mixFactor);
mixColor.a = Mathf.Lerp(color0.a, color1.a, mixFactor);
return mixColor;
}

private Texture2D _GenerateProceduralTexture() {
Texture2D proceduralTexture = new Texture2D(textureWidth, textureWidth);

// The interval between circles
float circleInterval = textureWidth / 4.0f;
// The radius of circles
float radius = textureWidth / 10.0f;
// The blur factor
float edgeBlur = 1.0f / blurFactor;

for (int w = 0; w < textureWidth; w++) {
for (int h = 0; h < textureWidth; h++) {
// Initalize the pixel with background color
Color pixel = backgroundColor;

// Draw nine circles one by one
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
// Compute the center of current circle
Vector2 circleCenter = new Vector2(circleInterval * (i + 1), circleInterval * (j + 1));

// Compute the distance between the pixel and the center
float dist = Vector2.Distance(new Vector2(w, h), circleCenter) - radius;

// Blur the edge of the circle
Color color = _MixColor(circleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1.0f, dist * edgeBlur));

// Mix the current color with the previous color
pixel = _MixColor(pixel, color, color.a);
}
}

proceduralTexture.SetPixel(w, h, pixel);
}
}

proceduralTexture.Apply();

return proceduralTexture;
}
}

Unity的程序材质

使用Substance Designer来生成纹理,使用该程序纹理得到程序材质(Procedural Materials)

可以导入外部的程序纹理后,在Unity中调节出不同效果


UnityShader-高级纹理
http://example.com/2023/01/10/UnityShader-高级纹理/
作者
Chen Shuwen
发布于
2023年1月10日
许可协议