Unity Shader总结(十)——Cubemap、镜子、玻璃、程序纹理_战机少女zhu的博客-程序员宝宝_unity镜面材质

技术标签: unity3d  Unity Shader入门  


Cubemap是环境映射的一种实现方法,使用三维纹理坐标进行采样,这个三维纹理坐标代表世界空间下的一个3D方向,这个方向矢量从立方体中心出发,采样的结果由交点计算得出。
缺点:1.当场景引入新的物体、光源或物体发生移动时,都要重新计算;2.仅可以反射环境,不可以反射使用了Cubemap的物体自身,因为Cubemap不能模拟多次反射,如两个金属球互相反射(全局光照可以实现),因此尽量对凸面体使用,不要对凹面体使用,因为凹面体会反射自身。

天空盒

1.新建材质;
2.在材质的unity shader下选择skybox/6 Sided;
3.把6张纹理的wrap mode设置为Clamp,防止接缝处出现不匹配的现象;
4.把材质赋给lighting中的skybox;
5.将camera中的clear flags设置为skybox;
6.如果希望某些摄像机使用不同的天空盒,可以在摄像机上单击component——rendering——skybox来完成覆盖;

创建立方体纹理

方法有三种:
1.准备一张HDR的立方体展开图,纹理类型设置cubemap;(推荐)
2.准备六张纹理贴到创建的Cubemap上;
3.脚本创建;

脚本创建通过Camera.RenderToCubemap实现:

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!");
	}
}

该脚本将摄像机放到物体位置观察到的六张图像渲染到cubemap中,使用方法:创建gameobject,再创建cubemap(legacy->cubemap),勾选readable,最后打开Render into Cubemap功能渲染即可得到立方体纹理,face size代表渲染质量。

环境映射

反射

把前面得到的cubemap_0放到Reflection Cubemap属性中

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 10/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"
}

折射

斯涅尔定律:

η 1 s i n Θ 1 = η 2 s i n Θ 2 \eta _{1}sin\Theta _{1}=\eta _{2}sin\Theta _{2} η1sinΘ1=η2sinΘ2
Θ \Theta Θ是光线和法线夹角,折射率:真空为1,玻璃一般为1.5;

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 10/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"
}

菲涅尔反射

菲涅尔反射是指一部分发生反射,一部分发生折射;被反射的光和入射光之间存在一定比率关系;如:近处的水面可以看到湖底,远处只能看到水面反射。

Schlick菲涅耳近似等式:
F S c h l i c k ( υ , n ) = F 0 + ( 1 − F 0 ) ( 1 − υ ⋅ n ) 5 F_{Schlick}(\upsilon,n)=F_{0}+(1-F_{0})(1-\upsilon\cdot n )^{5} FSchlick(υ,n)=F0+(1F0)(1υn)5
F 0 F_{0} F0是反射系数,控制菲涅耳反射强度。使用近似等式可以在边界处模拟反射光强和折射光强/漫反射光强之间的变化,常用的有车漆、水面渲染。
_FresnelScale为0时边缘发光,为1时完全反射Cubemap中的图像;

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 10/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"
}

在这里插入图片描述

渲染纹理(RenderTexture)

现代GPU允许将整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(Render Target Texture,RTT),渲染纹理(RT)就是Unity为RTT定义的一种纹理类型。
使用渲染纹理的两种方式:
1.在Project中创建一个渲染纹理,将摄像机的渲染目标设置为该RT;
2.在屏幕后处理时使用GrabPass或OnRenderImage函数来获取当前屏幕图像,Unity会把这个屏幕图像放到一张和屏幕分辨率等同的一张渲染纹理中;

镜子效果

在Project中创建一个渲染纹理,创建一个摄像机,将摄像机的渲染目标设置为该RT;
再把该RT拖到材质中的maintex中;

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 10/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
}

在这里插入图片描述

玻璃效果

使用GrabPass注意要将渲染队列设置为Transparent;保证先绘制不透明物体。
方法:1.使用一张法线纹理修改模型的法线信息;2.使用Cubemap模拟反射;3.使用GrabPass获取玻璃后面的屏幕图像,并使用切线空间下的法线对屏幕纹理坐标偏移后,再对屏幕图像进行采样来模拟近似的折射效果。

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 10/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.
		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
		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;
			//使用GrabPass时指定的纹理名称,可以不用名称,但使用名称时更省性能
			sampler2D _RefractionTex;
			//得到该纹理的纹素大小,如纹理大小为256*512,纹素大小为(1/256,1/512),在对屏幕图像的采样坐标偏移时使用该变量
			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.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
				//使用该坐标对_RefractionTex采样,得到模拟的折射颜色
				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"
}

把书中提到的diffuse纹理和normal纹理分别给main tex和normal map属性,再把使用render to cubemap创建的材质赋给environment cubemap;

GrabPass支持两种形式:
1.直接使用GrabPass{},在后续的Pass中直接使用——GrabTexture来访问屏幕图像,但是当有多个物体使用这样的方法会消耗性能,这样可以让每个物体得到不同的屏幕图像。
2.使用GraPass{“TextureName”},这样unity只会在每一帧第一个使用这个名字的纹理的物体抓取屏幕,这个纹理也可以被其他Pass访问,更高效,但是所有物体都会使用同一张屏幕图像;

渲染纹理和GrabPass的不同

在移动设备上,渲染纹理更有效率,因为在高分辨率设备上,GrabPass抓取的图像分辨率和显示屏幕一致,会造成严重的带宽影响,而且它需要直接读取后备缓冲中的数据,破坏了CPU和GPU之间的并行性,比较耗时;

程序纹理

程序纹理是指用计算机生成的图像,通常使用一些特定的算法来创建个性化图案或者非常真实的自然元素,例如木头、石子等。使用程序纹理的好处是我们可以使用各种参数来控制纹理的外观。

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

[ExecuteInEditMode]
public class ProceduralTextureGeneration : MonoBehaviour {
    

	public Material material = null;

	#region Material properties
	//SetProperty开源插件,当修改了材质属性时,可以使用_UpdateMaterial函数使用新的属性重新生成程序纹理
	[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();
			//把纹理赋给材质,材质中需要有叫_MainTex的纹理属性
			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++) {
    
				// 初始化背景颜色
				Color pixel = backgroundColor;

				// 绘制9个圆
				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);
			}
		}
		//把像素值写入纹理
		proceduralTexture.Apply();

		return proceduralTexture;
	}
}

程序材质

程序材质和程序纹理是使用软件Substance Designer生成,材质通常以.substar为后缀,和平时的材质一样使用,通过调整程序纹理的属性可以得到完全不同的外观,是一种非常强大的材质类型。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/memory_MM_forever/article/details/118277022

智能推荐

Android Tools集合下载_松狮MVP的博客-程序员宝宝

1、ANDROID——RecyclerView,CardView导入和使用(Demo):http://blog.csdn.net/vrix/article/details/441780112、Android Dev Tools官网地址:www.androiddevtools.cn收集整理Android开发所需的Android SDK、开发中用到的工具、Android开发教程、Android

QNX_junecau的博客-程序员宝宝

QNX求助编辑百科名片  QNX界面Gordon Bell和Dan Dodge在1980年成立了Quantum Software Systems公司,他们根据大学时代的一些设想写出了一个能在IBM PC上运行的名叫QUNIX(Quick U

ad用户如何和计算机名对应,计算机加域和AD里相应对象的问题_实话直说的博客-程序员宝宝

环境:win 2008 r2 配置域,域中客户端通过mdt 2010 + WDS分发系统,遇到如下问题,想咨询到底是什么原因引起:1、计算机A加域,在AD中有计算机对象A,假如A出现问题需重装,我先删除AD中计算机对象,然后再通过MDT分发,在MDT初始设置中将计算机名设为A,系统安装完成后,查看其状态,域中,使用域ID能正常登陆,但是在AD中始终找不到A这个对象。问题:为什么出现这种情况,加域后...

Python爬取豆瓣电影信息并分析_zzadata的博客-程序员宝宝_python爬取豆瓣电影信息

Python爬取豆瓣电影信息并分析豆瓣博主整理了每年的热门电影榜单榜单基于 豆瓣评分+评价人数+影片类型+发行时间 综合排序点击查看网页一、爬取2009年到2019年的热门电影数据1.先查看网页源代码找到存放电影信息的代码:class=‘title’的div标签文本中存放着电影名称class=‘rating’的div标签中存放着星级,评分和评价人数class=‘abstract’的div标签中存放着导演,主演,类型,制片国家/地区和年份的信息根据上述获得的信息即可进行爬虫...

淦ORB-SLAM2源码 10--最小二乘问题(最速下降法,牛顿法,高斯牛顿,LM法,鲁棒核函数)_安德鲁JANKENPAN的博客-程序员宝宝_柯西鲁棒核函数

最小二乘问题最小二乘问题最速下降法牛顿法高斯牛顿LM法最小二乘问题最速下降法牛顿法高斯牛顿LM法

【安全狐】Nmap,Masscan扫描软件 安装教程和基本使用_安全狐的博客-程序员宝宝_nmap下载

Nmap 初步学习 简介Nmap是一个 [网络连接端扫描软件,用来扫描网上电脑开放的网络连接端。确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统(这是亦称 fingerprinting)。它是网络管理员必用的软件之一,以及用以评估网络系统安全] 。正如大多数被用于网络安全的工具,Nmap 也是不少黑客及骇客(又称脚本小子)爱用的工具 。系统管理员可以利用Nmap来探测工作环...

随便推点

软件测试工程师又一大挑战:大数据测试_魔都飘雪的博客-程序员宝宝_大数据测试和软件测试区别

什么是大数据大数据是指无法在一定时间范围内用传统的计算机技术进行处理的海量数据集。对于大数据的测试则需要不同的工具、技术、框架来进行处理。大数据的体量大、多样化和高速处理所涉及的数据生成、存储、检索和分析使得大数据工程师需要掌握极其高的技术功底。需要你学习掌握更多的大数据技术、Hadoop、Mapreduce等等技术。大数据测试策略大数据应用程序的测

HDU2256&HDU5451(矩阵快速幂)_DS_HY的博客-程序员宝宝

求(5+2)^n引用博客https://blog.csdn.net/qq_15714857/article/details/47705581https://blog.csdn.net/qq_15714857/article/details/48583019 HDU2256时为什么不能直接用An+Bn 求F(n) ,因为Bn求时取模了,再乘一个小数,再取模之后,不是原来的值的取模...

初识mvc分层思想_dengtuo7157的博客-程序员宝宝

首先要清楚的是:mvc是一种设计模式,一种分层思想,没有具体的技术与之对应,无论是js还是java或者其他的技术都可以运用。既然是分层那么这些层都有哪些职责呢?View层(界面层):为用户展示数据,渲染由controller层和module层处理完的数据。Controller层(业务逻辑层):接收界面层的数据,对接受到的数据进行封装和类型转换。调用模型层的服务进行...

解决“产品运行所需的信息检索失败。请重新安装xshell”_阿呆的博客的博客-程序员宝宝_产品运行所需的信息检索失败

使用管理员身份运行里面的绿化bat文件就可以了。链接:https://pan.baidu.com/s/1XdzHjEx4AMM4BemQTqZQJw提取码:aw41转自(https://blog.csdn.net/lezeqe/article/details/94878039)...

多用户与多租户的区别_迷路剑客的博客-程序员宝宝_多租户和多用户区别

多用户与多租户的区别转载声明本文大量内容系转载自以下文章,有删改,并参考其他文档资料加入了一些内容:多用户与多租户的区别作者:满城风絮2013出处:cnblogs看图秒懂多租户,形象具体!作者:openfea出处:csdn如何理解多租户架构?作者:平凡21出处:cnblogs1 多用户现代软件一般属于多用户的应用,也就是说,同一台机器同一套软件可以为多个用户建立各自的账户,也允许拥有这些账户的用户同时登录这台计算机。这就涉及到计算机用户和资源的管理。简单地说就

推荐文章

热门文章

相关标签