Unity-shader学习笔记(五)

Unity-shader学习笔记(五)

这里我们会聊聊基础纹理和透明效果的知识。

13 基础纹理

我们用纹理干什么呢?使用它来控制模型的外观,使用的是纹理映射技术来让纹理与模型表面贴住,在逐纹素地控制模型颜色。

在建模时,美术人员会将纹理映射坐标存储在每一个顶点上,它定义了该顶点在纹理中对应的2D坐标。通常这些坐标使用一个二位变量(u,v)来表示,u为横向坐标,v是纵向坐标。这也是我们所称的UV坐标。

我们通常将UV坐标的范围归一化到[0,1]范围内,但在纹理采样时使用的纹理坐标不一定在[0,1]内,因为在纹理的平铺模式下,[0,1]之外的纹理坐标与[0,1]之内的纹理坐标的纹理采样是不同的。

但似乎你还是不知道纹理是什么?纹理坐标又代表什么?

13.1 纹理与纹理坐标

纹理可以理解为一个物体身上颜色以及物体表面的光滑和粗糙情况。

而纹理坐标呢?

它实际上是一个二维数组,它的元素是一些颜色值。单个颜色值被称为纹理元素或纹素,每一个纹理像素在纹理中都有一个唯一的地址,也就是我们之前所说UV坐标。

当我们将一个纹理应用于一个图元时,它的纹理像素地址必须要映射到对象坐标中,然后再平移到屏幕坐标系或像素位置上去。

13.2 单张纹理

在这个例子中,我们使用Blinn-Phong光照模型进行计算:

①在高光反射的Properties中多增加一项纹理属性:

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

我们声明了一个名为_MainTex的纹理,使用一个字符串和一个花括号作为它的初值。

②在CG代码块中声明与上述属性相匹配的变量:

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;

对第三个变量_MainTex_ST,这是为纹理类型的属性声明的。

_MainTex_ST.xy存储的是纹理的缩放值;

_MainTex_ST.zw存储的是纹理的偏移值。

这两个值都可以在材质面板的纹理属性中调节。

③定义顶点着色器的输入和输出结构体:

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

struct v2f
{
	float4 pos : SV_POSITION;
	float3 worldNormal : TEXCOORD0;
	float3 worldPos : TEXCOORD1;
	float2 uv : TEXCOORD2;
};

在a2v中使用TEXCOORD0语义声明变量texcoord,使Unity将模型的第一组纹理坐标存储到该变量中。

在v2f中添加用于存储纹理坐标变量的uv,以便在片元着色器中使用该坐标进行纹理采样。

有的朋友可能会说,为什么worldMormal和worldPos要声明成纹理坐标?因为这整个shader都是为纹理采样服务的,我们将纹理坐标映射到顶点上去后,依然是要对顶点的值进行反射处理。

④定义顶点着色器:

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;

	return o;
}

先使用缩放属性_MainTex_ST.xy将纹理坐标映射到顶点坐标上去,再使用偏移属性 _MainTex_ST.zw对结果进行偏移。这样之后纹理就能显示在屏幕坐标系中了。

⑤最后在片元着色器中计算光照反射模型,这里使用的就不再是顶点的原始坐标,而是纹理映射后的坐标。

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

	fixed3 aldebo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
	
    //自然光
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * aldebo;
	//漫反射
	fixed3 diffuse = _LightColor0.rgb * aldebo * 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);
}

最主要的区别就是使用tex2D函数对纹理进行采样:第一个参数是需要被采样的纹理,第二个参数是存储返回计算得到的纹素值的变量。

材质最终反射出的颜色就是采样的结果与颜色属性_Color的乘积。在计算环境光时,就不仅仅像之前的只是一个UNITY_LIGHTMODEL_AMBIENT.xyz了,也要乘上材质反射出的颜色。

高光反射的计算依然没有变化,只是每个值的内容变化了而已。

当然最后别忘了Fallback。

13.3 纹理的属性

这样写完之后你是不是会感慨:原来纹理映射是如此的简单。但实际上,在渲染流水线中,纹理映射的实现比我们所想象的要复杂得多。接下俩就会聊聊关于纹理属性的知识。

其实也主要是了Wrap Mode和Filter Mode的内容,它决定了当纹理坐标超过[0,1]范围后将会如何被平铺。

(1)在笔者使用的Unity2018.4.27中Wrap Mode一共有五种类型:

①Repeat重复模式

在这种模式下,如果纹理坐标超过了1,那么它的整数部分就会被舍弃而直接使用小数部分进行采样,结果就是纹理会不断重复;

②Clamp边缘拉伸模式

在这种模式下,如果纹理坐标超过了1,就会截取到1,如果小于0,就会截取到0;

③Mirror镜像模式

超出的部分将以镜像的方式展开

④Mirror Once一次镜像

字面理解起来是镜像一次,但不知道为什么它的效果跟Camp的效果是一样的;

⑤Per-axis平铺

分别对U、V方向超出部分分开做平铺处理。

(2)Filter Mode属性,它决定了当纹理由于变换而产生拉伸时将会采用哪种滤波器。它有三种模式:Point、Bilinear、Trilinear,它们得到的图片的滤波效果一依次提升,但耗费的性能也依次增大,

有朋友可能不理解滤波是干嘛的。你回想自己在放大或缩小一张图片时,总是会影响图片的清晰度(质量),这就是由滤波影响的。

既然提到了图片的缩小和放大,那就扩展地说说吧。

缩放就是原纹理中多个像素会对应一个目标像素,它比放大更加复杂一点,主要就在处理抗锯齿问题。处理它的主要办法就是多级渐远纹理技术

它的原理是:将原纹理提前用滤波处理得到很多更小的图片,形成一个图像金字塔,每一层就是对上一层图像降采样的结果。

通过这种技术,就能快速得到结果像素。例如,当物体远离摄像机时就可以直接使用较小的纹理。当然缺点也很明显,就是需要额外的空间来存储每层的较小的纹理。(非常典型的用空间换时间的方法)

Point使用的是nearest neighbor滤波(最邻近滤波),在放大或缩小时它的采样像素数目通常只有一个,这也就导致放大或缩小后看起来会有像素风格;

Bilinear使用的是Linar滤波(线性滤波),对于每个目标像素,它会找到四个邻近像素,找到后会对它们进行线性插值混合,最后的效果看起来像是被模糊了;

Trilinear使用的也几乎时线性滤波,但后者只是会多出在多级渐远纹理之间的进行混合。如果一个纹理没有使用多级渐远纹理技术,那么这两种模式的结果是一样的。

13.4 凹凸映射

正如其名,这种映射会让模型看起来凹凸不平——使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。要注意,这种映射并不会改变模型的顶点位置。

有两种方法:

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值