Unity Shader学习记录(17) —— 高级纹理

文章详细介绍了Unity中的立方体纹理,包括其在环境映射中的应用,如天空盒和反射。接着讨论了折射的模拟,菲涅耳反射的原理和实现,以及如何在Shader中创建玻璃效果。此外,还提到了渲染纹理的概念,如GrabPass和渲染目标纹理在实现镜子和玻璃效果中的作用。最后,文章简述了程序纹理和Unity的程序材质。
摘要由CSDN通过智能技术生成

立方体纹理

在图形学中,立方体纹理(Cubemap)是环境映射(Environment Mapping)的一种实现方法。环境映射可以模拟物体周围的环境,而使用了环境映射的物体可以看起来像镀了层金属一样反射出周围的环境。

和之前见到的纹理不同,立方体纹理一共包含了 6 张图像,这些图像对应了一个立方体的个面,立方体纹理的名称也由此而来。立方体的每个面表示沿着世界空间下的轴向(上、下、左右、前、后)观察所得的图像。

使用立方体纹理的好处在于,它的实现简单快速,而且得到的效果也比较好。但它也有一些缺点,
1 例如当场景中引入了新的物体、光源,或者物体发生移动时,我们就需要重新生成立方体纹理。
2 除此之外,立方体纹理也仅可以反射环境,但不能反射使用了该立方体纹理的物体本身。这是因为,立方体纹理不能模拟多次反射的结果,例如两个金属球互相反射的情况(由于这样的原因,想要得到令人信服的渲染结果,我们应该尽量对凸面体而不要对凹面体使用立方体纹理(因为凹面体会反射自身)。

立方体纹理在实时渲染中有很多应用,最常见的是用于天空盒子(Skvbox)以及环境映射

天空盒子

创建天空盒子材质,贴图后放置到场景内。
六面贴图需要设置为clamp
摄像机Clear Flags需要设置为skybox
在这里插入图片描述
Window- Lighting- Skybox中设置的天空盒子会应用于该场景中的所有摄像机(在这个位置上会创建一个动态的摄像机拍摄周围环境)。如果我们希望某些摄像机可以使用不同的天空盒子,可以通过向该摄像机添加 Skybox组件来覆盖掉之前的设置。也就是说,我们可以在摄像机上单击 Component - RenderingSkybox来完成对场景默认天空盒子的覆盖。

环境映射的立方体纹理

创建用于环境映射的立方体纹理的方法有三种:
第一种方法是直接由一些特殊布局的纹理创建: 需要一张展开UV的贴图
第二种方法是手动创建一个Cubemap ( 在legacy下面 )资源,再把6张图赋给它:使用六张贴图
第三种方法是由脚本生成:利用 Unity 提供的Camera.RenderToCubemap 函数来实现的。Camera.RenderToCubemap 函数可以把从任意位置观察到的场景图像存储到6张图像中,从而创建出该位置上对应的立方体纹理。
在这里插入图片描述
在上面的代码中,我们在renderFromPosition (由用户指定)位置处动态创建一个摄像机,并调用CameraRenderToCubemap 函数把从当前位置观察到的图像渲染到用户指定的立方体纹理cubemap 中,完成后再销毁临时摄像机。由于该代码需要添加菜单栏条目,因此我们需要把它放在Editor文件夹下才能正确执行。
(实际上有bug,editor文件需要去官网下载替换 https://gitcode.net/mirrors/LMNRY/SetProperty?utm_source=csdn_github_accelerator )

渲染烘焙出一张纹理

使用烘焙纹理

折射

我们将学习如何在 Unity Shader 中模拟另一个环境映射的常见应用一-折射。折射的物理原理比反射复杂一些。我们在初中物理就已经接触过折射的定义:当光线从一种介质(例如空气)斜射入另一种介质(例如玻璃)时,传播方向一般会发生改变。当给定入射角时,我们可以使用斯涅尔定律(Smells Law)来计算反射角。当光从介质1沿着和表面法线夹角为0的方向斜射入介质2时,我们可以使用如下公式计算折射光线与法线的夹角:
在这里插入图片描述

通常来说,当得到折射方向后我们就会直接使用它来对立方体纹理进行采样,但这是不符合物理规律的。对一个透明物体来说,一种更准确的模拟方法需要计算两次折射一- 一次是当光线进入它的内部时,而另一次则是从它内部射出时。但是,想要在实时渲染中模拟出第二次折射方向是比较复杂的,而且仅仅模拟一次得到的效果从视觉上看起来“也挺像那么回事的”。(其实就通过折射率换算一下角度,然后以这个角度进行纹理采样。)

Shader "MyShader/10-Reflection"
 {
	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
			//unity专门为forwardbase预定义的multi_compile,控制阴影和衰减
			#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);
				
				// 计算世界空间下折射向量
				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));
				
				// 使用折射向量采样立方体纹理
				fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;
				
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				
				// 混合反射光和漫射光
				fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
				
				return fixed4(color, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Reflective/VertexLit"
}

菲涅尔

在实时染中,我们经常会使用菲涅耳反射(Fresnelreflection)来根据视角方向控制反射程度。通俗地讲,菲涅耳反射描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅耳等式进行计算。一个经常使用的例子是,当你站在湖边,直接低头看脚边的水面时,你会发现水几乎是透明的,你可以直接看到水底的小鱼和石子:但是,当你抬头看远处的水面时,会发现几乎看不到水下的情景,而只能看到水面反射的环境。这就是所谓的菲涅耳效果。

在实时渲染中,我们通常会使用一些近似公式来计算。其中一个著名的近似公式是Schlick菲涅耳近似等式:
在这里插入图片描述
在这里插入图片描述

Shader "Unlit/10-Fresnel"
{
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
        //反射颜色
		_FresnelScale ("Fresnel Scale", Range(0,1)) = 0.5
        //纹理
		_Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {}
	}
	SubShader {

        //不透明物体,优先渲染
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		
		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			//unity专门为forwardbase预定义的multi_compile,控制阴影和衰减
			#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);
				
				// 计算世界空间下折射向量
				o.worldRefr = 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));
                //菲涅尔近似不等式计算反射
				fixed fresnel= FresnelScale + (1 - FresnelScale) * pow(1 - dot(worldViewDir, worldNormal),5);
				// 使用折射向量采样立方体纹理
				fixed3 reflection = texCUBE(_Cubemap, i.worldRefr).rgb;
				
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				
				// 混合反射光和漫射光
				fixed3 color = ambient + lerp(diffuse,reflection, saturate(fresnel)) * atten;
				
				return fixed4(color, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Reflective/VertexLit"
}

渲染纹理

渲染纹理在之前的学习中,一个摄像机的渲染结果会输出到颜色缓冲中,并显示到我们的屏幕上。现代的GPU允许我们把整个三维场景染到一个中间缓冲中,即目标纹理(Render TargetTexture,RTT),而不是传统的缓冲或后备缓冲(back buffer)。与之相关的是多重渲染目标Multiple Render Target,MRT),这种技术指的是GPU允许我们把场景同时染到多个渲染目标纹理中,而不再需要为每个渲染目标纹理单独渲染完整的场景。延迟渲染就是使用多重渲染目标的一个应用。

Unity为渲染目标纹理定义了一种专门的纹理类型一渲染纹理(Render Texture)。在Unity中使用渲染纹理通常有两种方式:一种方式是在Project目录下创建一个染纹理,然后把某个摄像机的渲染目标设置成该渲染纹理,这样一来该摄像机的渲染结果就会实时更新到渲染纹理中,而不会显示在屏幕上。使用这种方法,我们还可以选择渲染纹理的分辨率、滤波模式等纹理属性另一种方式是在屏幕后处理时使用GrabPass 命令或OnRenderlmage 函数来获取当前屏幕图像Unitv 会把这个屏幕图像放到一张和屏幕分辨率等同的染纹理中,下面我们可以在自定义的Pass中把它们当成普通的纹理来处理,从而实现各种屏幕特效。

镜子效果

目标纹理:可以把摄像机看到的图像转化为纹理储存。
纹理采样,将uv采样的x反转为-x得到镜像反转的效果,采样摄像机看到的图像。
shader代码不难理解,需要一张渲染纹理作为材质采样即可。(摄像机 —— 渲染纹理 —— 材质 —— 物体)
在这里插入图片描述

Shader "MyShader/10-Mirror"
{
	Properties {
		_MainTex ("Main Tex", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		
		Pass {
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			
			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
}

玻璃效果

在Unity中我们还可以在 Unity Shader 中使用一种特殊的 Pass来完成获取屏幕图像的目的,这就是GrabPass。当我们在Shader 中定义了一个GrabPass后,Unity会把当前屏幕的图像绘制在一张纹理中,以便我们在后续的 Pass 中访问它。我们通常会使用GrabPass 来实现诸如玻璃等透明材质的模拟,与使用简单的透明混合不同,使用GrabPass可以让我们对该物体后面的图像进行更复杂的处理,例如使用法线来模拟折射效果,而不再是简单的和原屏幕颜色进行混合。

GrabPass { “_RefractionTex” } 用来在屏幕空间将当期屏幕的图像转化为屏幕采样纹理,

Shader "MyShader/10-GlassRefraction"
 {
	Properties {
        //玻璃材质纹理
		_MainTex ("Main Tex", 2D) = "white" {}
        //玻璃的法线贴图
		_BumpMap ("Normal Map", 2D) = "bump" {}
        //环境纹理
		_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
        //折射扭曲程度
		_Distortion ("Distortion", Range(0, 1000)) = 10
        //折射程度 0只有反射,1只有折射,折射包含透明
		_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0
	}
	SubShader {
		// 透明渲染队列 确保先渲染非透明物体
		Tags { "Queue"="Transparent" "RenderType"="Opaque" }
		
		// Grabpass获取屏幕图像转化为贴图
		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;
			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; 
			};
			//在进行了必要的顶点坐标变换后,我们通过调用内置的ComputeGrabScreenPos函数来得到对应被抓取的屏幕图像的采样坐标。读者可以在 UnityCGcginc 文件中找到它的声明,它的主要代码和ComputeScreenPos基本类似,最大的不同是针对平台差异造成的采样标问题(详见5.6.1节)进行了处理
			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; 
				// 计算切线空间转世界空间的变换矩阵,每一行分别储存到TtoW0,TtoW1,TtoW2
				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));
				
				// 凹凸纹理采样
				fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));	
				
				// 偏移纹理采样
				float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy; 
				// 偏移采样,与v坐标相关,
				i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
				// 采样屏幕RT纹理的到反射颜色
				fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
				
				// 将法线转换到世界空间
				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,然后在后续的Pass中直接使用GrabTexture来访问屏幕图像。但是,当场景中有多个物体都使用了这样的形式来抓取屏幕时,这种方法的性能消耗比较大,因为对于每一个使用它的物体,Unity 都会为它单独进行一次昂贵的屏幕抓取操作。但这种方法可以让每个物体得到不同的屏幕图像,这取决于它们的渲染队列及渲染它们时当前的屏幕缓冲中的颜色。

使用GrabPass{“TextureName”,正如本节中的实现,我们可以在后续的 Pass中使用TextureName来访问屏幕图像。使用这种方法同样可以抓取屏幕,但Unity 只会在每一帧时为第一个使用名为TextureName的纹理的物体执行一次抓取屏幕的操作而这个纹理同样可以在其他 Pass 中被访问。这种方法更高效,因为不管场景中有多少物体使用了该命令,每一帧中Unity 都只会执行一次抓取工作,但这也意味着所有物体都会使用同一张屏幕图像。不过,在大多数情况下这已经足够了。

渲染纹理 和 GrabPass区别

尽管GrabPass和 10.2.1节中使用的染纹理 + 额外摄像机的方都可以抓取屏幕图像,但它们之间还是有一些不同的。GrabPass 的好处在于实现简单,我们只需要在Shader 中写几行代码就可以实现抓取屏幕的目的。而要使用渲染纹理的话,我们首先需要创建一个渲染纹理和一个额外的摄像机,再把该摄像机的 Render Target 设置为新建的染纹理对象,最后把该染纹理传递给相应的Shader。

但从效率上来讲,使用渲染纹理的效率往往要好于 GrabPass,尤其在移动设备上。使用渲染纹理我们可以自定义渲染纹理的大小,尽管这种方法需要把部分场景再次渲染一遍,但我们可以通过调整摄像机的渲染层来减少二次渲染时的场景大小,或使用其他方法来控制摄像机是否需要开启。而使用GrabPass 获取到的图像分辨率和显示屏幕是一致的,这意味着在一些高分辨率的设备上可能会造成严重的带宽影响。而且在移动设备上,GrabPass 虽然不会重新染场景,但它往往需要CPU直接读取后备缓冲(back buffer)中的数据,破坏了CPU和GPU之间的并行性,这是比较耗时的,甚至在一些移动设备上这是不支持的。

程序纹理

程序纹理(ProceduralTexture)指的是那些由计算机生成的图像,我们通常使用一些特定的算法来创建个性化图案或非常真实的自然元素,例如木头、石子等。使用程序纹理的好处在于我们可以使用各种参数来控制纹理的外观,而这些属性不仅仅是那些颜色属性,甚至可以是完全不同类型的图案属性,这使得我们可以得到更加丰富的动画和视觉效果。在本节中,我们首先会尝试用算法来实现一个非常简单的程序材质。然后,我们会介绍 Unity 里一类专门使用程序纹理的材质一-程序材质。

在这里插入图片描述

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

[ExecuteInEditMode]
public class ProceduralTextureGeneration : MonoBehaviour {

    // 材质声明
	public Material material = null;
    // 纹理参数
	#region Material properties
    // 纹理宽度参数声明    SetProperty控件设置属性
	[SerializeField, SetProperty("textureWidth")]
    // 纹理宽度
	private int m_textureWidth = 512;
	public int textureWidth {
		get {
			return m_textureWidth;
		}
		set {
			m_textureWidth = value;
			_UpdateMaterial();
		}
	}
    //纹理背景颜色声明  SetProperty控件设置属性
	[SerializeField, SetProperty("backgroundColor")]
	private Color m_backgroundColor = Color.white;
	public Color backgroundColor {
		get {
			return m_backgroundColor;
		}
		set {
			m_backgroundColor = value;
			_UpdateMaterial();
		}
	}
    //圆环颜色声明  SetProperty控件设置属性
	[SerializeField, SetProperty("circleColor")]
	private Color m_circleColor = Color.yellow;
	public Color circleColor {
		get {
			return m_circleColor;
		}
		set {
			m_circleColor = value;
			_UpdateMaterial();
		}
	}

    // 弥散程度声明   SetProperty控件设置属性
	[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;

	// 初始化
	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) {
            //更新纹理并赋值生成的纹理set给m_generatedTexture纹理
			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);

		// 间隔
		float circleInterval = textureWidth / 4.0f;
		// 半径
		float radius = textureWidth / 10.0f;
		// 弥散程度
		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;

				// 依次画出九个圆
				for (int i = 0; i < 3; i++) {
					for (int j = 0; j < 3; j++) {
						// 计算当前圆心位置
						Vector2 circleCenter = new Vector2(circleInterval * (i + 1), circleInterval * (j + 1));

						// 计算当前像素与圆心的距离
						float dist = Vector2.Distance(new Vector2(w, h), circleCenter) - radius;

						// 模糊当前圆
						Color color = _MixColor(circleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1.0f, dist * edgeBlur));

						//与之前的到的颜色混合
						pixel = _MixColor(pixel, color, color.a);
					}
				}

				proceduralTexture.SetPixel(w, h, pixel);
			}
		}
        // Apply函数来强制把像素值写入纹理中
		proceduralTexture.Apply();

		return proceduralTexture;
	}
}

Unity的程序材质

有一类专门使用程序纹理的材质,叫做程序材质(ProceduralMaterials)。这类材质和我们之前使用的那些材质在本质上是一样的,不同的是,它们使用的纹理不是普通的纹理,而是程序纹理。需要注意的是,程序材质和它使用的程序纹理并不是在 Unity 中创建的,而是使用了一个名为Substance Designer 的软件在Unity外部生成的。

类似程序纹理,在资源库下载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值