【Unity Shader】(5)基础纹理 将图片映射到材质上(代码部分)

将单张纹理映射到材质球


本章节分为代码部分和概念部分,概念部分明天再写,今天先开摆!

在最初的游戏行业,游戏中的物体都是使用一张图片来控制模型外观的,将“纹理映射”直接“黏”在 模型的表面,再通过“逐纹素(texel)”来控制模型的颜色。这里的“纹素”其实就是为了区分“像素”而诞生的名词,在纹理中的每个单位,即为纹素。

根据上文,可以轻松的得知,我们需要如何将贴图(也就是我们的纹理图片)映射到模型上——通过纹理展开技术,将纹理映射坐标存储在每一个顶点上。

纹理的坐标

还有一个重要的便是unity中的“纹理贴图”的坐标,没搞懂坐标,可能在之后会发现贴图的效果与自己预想的恰好相反。在纹理贴图中,一般默认使用二维变量(u,v)来表示二维坐标,而不是xy。
Unity的shader纹理贴图与OpenGL的贴图是类似的——都是以左下角为原点,u的正方向向右,v的正方向向上,如下图所示。(而对于DirectX来说,恰恰相反——原点在左上角,v的正方向向下,u的正方向向右)

Unity中的UV坐标:
1

值得一提的是,Unity可能会自动帮我们处理纹理贴图,当然有可能不会,请看下面三种情况:

  • 如果系统没有打开抗锯齿(在Unity的 Project Settings → Quality → Anti Alisasing是否勾选),Unity会自动为我们调整纹理贴图的方向。这样就可以确保Unity在OpenGL和DirectX的平台都适配。(这里的自动调整,会根据玩家自己的电脑平台,来调整是否旋转以达到正确预期)
  • 但是如果打开了抗锯齿,且渲染的是单张贴图,那么Unity会根据Graphics.Blit()函数继续帮我们调整贴图方向。
  • 但是如果打开了抗锯齿,且渲染的是多张贴图,那么Unity可能在DirectX平台上就束手无策了,这个时候就需要我们自己来判断用户的平台来处理判断。代码如下:

(开启抗锯齿后,在DirectX平台中,_MainTexelSize.y的值将会小于0。但是对于装饰性的纹理贴图来说,不必太在意是否翻转)

#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0)
	uv.y = 1 - uv.y;
#endif



纹理的大小

纹理的大小可能是不尽相同的(256×256、1024×1024),但最终都会进行归一化处理成 [0,1] 的范围内。虽然在纹理采样的时候可能不会严格按照 [0,1] 的范围进行采样(这个稍后谈到)。
在Unity中,会自动为我们调整纹理坐标(情况就是上述所讲的三种抗锯齿情况)。

纹理资源的相关属性(笔记汇总)

123



代码开始
  • 单张纹理

1. 创建一个空场景(只有摄像机和平行光),删除该场景中的天空盒。

2. 新建一个材质,命名为Mat_Texture

3. 新建一个UnityShader,命名为Sha_Texture并将该Shader赋值给Mat_Texture

4. 在场景中新建一个胶囊体,将Mat_Texture赋予该物体。


5. 保存,开始编辑Shader代码:

①删除第3步新建的Shader里的默认代码。
②我们以Blinn-Phong光照模型来计算光照。

③为了使用纹理,我们需要在Properties语义块中添加一个纹理属性:

Properties{
    
    
	_Color ("Base Color",Color) = (1,1,1,1)
	_MainTex("Main Texture",2D) = "white"{
    
     }
	_Specular("Specular",Color) = (1,1,1,1)
	_Gloss("Gloss",Range(8.0,256)) = 20
}

④光照继续使用ForwardBase
⑤设置顶点着色器vert和片元着色器frag,并包含Lighting.cginc
⑥初始化上述四个变量

float4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Specular;
half _Gloss;

这里的_MainTex_ST的名字并不是任意起的。在unity中,我们需要使用 纹理名_ST 的方式来声明某个纹理的属性。其中,ST分别是缩放Scale 和 平移Translation 的缩写。使用方法是:_MainTex_ST.xy 访问纹理缩放,_MainTex_ST.zw 访问纹理偏移。

⑦接下来,我们定义顶点着色器的输入和输出结构体:

struct a2v {
    
    
	float4 vertex : POSITION;
	float4 normal : NORMAL;
	float4 texcoord : TEXCOORD0;
};
struct v2f {
    
    
	float4 pos : SV_POSITION;
	float3 worldNormal : TEXCOORD0;
	float3 worldPos : TEXCOORD1;
	float2 uv : TEXCOORD2;
};

其中,TEXCOORD0会把第一组纹理坐标传递给texcoord,方便顶点着色器访问。且在v2f中,定义了uv方便我们在片元着色器使用该坐标进行纹理采样。

⑧然后,我们来定义顶点着色器:

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.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
	//_ST.xy -- 缩放		_ST.zw -- 位移

	return o;
}

上述代码首先计算了_MainTex_ST.xy的缩放,然后再使用_MainTex_ST.zw对结果进行偏移。(TRANSFORM_TEX可以帮我们计算上述过程。)

⑨实现片元着色器,计算漫反射时使用的纹理中的纹素值。

fixed4 frag(v2f i) : SV_Target{
    
    
	fixed3 worldNormal = normalize(i.worldNormal);
	fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
	fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
	fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
	fixed3 halfDir = normalize(worldLightDir + viewDir);
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

	return fixed4(ambient + diffuse + specular, 1.0);
}

上述代码首先根据Blinn-Phong公式计算了法线方向和光照方向。然后CG的tex2D函数对纹理进行采样。第一个是需要采样的纹理,第二个是计算返回的纹素值,再使用采样结果和颜色属性_Color的成绩来作为材质的 反射率albedo。

整体代码:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader"LeonShader/Shader_7_1_Texture"{
    
    
	Properties{
    
    
		_Color ("Base Color",Color) = (1,1,1,1)
		_MainTex("Main Texture",2D) = "white"{
    
     }
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256)) = 20
	}
	SubShader{
    
    
		Pass{
    
    
			Tags{
    
    "LightMode" = "ForwardBase"}

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			float4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float4 _Specular;
			half _Gloss;

			struct a2v {
    
    
				float4 vertex : POSITION;
				float4 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			struct v2f {
    
    
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

			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.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				//_ST.xy -- 缩放		_ST.zw -- 位移

				return o;
			}

			fixed4 frag(v2f i) : SV_Target{
    
    
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				return fixed4(ambient + diffuse + specular, 1.0);
			}


			ENDCG
		}
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_46840974/article/details/123999340
今日推荐