当前位置:   article > 正文

Unity3d Shader篇(七)— 纹理采样

Unity3d Shader篇(七)— 纹理采样


前言

纹理采样是一种常用的图形学技术,它可以让我们在渲染物体表面时,使用一张图片来提供颜色信息,从而增强物体的细节和真实感。在本文中,我们将介绍纹理采样的基本概念,原理和实现方法,以及在 Unity Shader 中如何使用纹理采样来实现一个简单的高光反射着色器。


一、什么是纹理采样?

1. 纹理采样的工作原理

纹理采样,就是通过 uv 获取贴图对应位置的颜色。uv 是一种二维坐标系,它的取值范围是 [0,1],表示了贴图上的相对位置。例如,uv 值是 (0.5,0.5),此时采样到的就是贴图中心点的颜色值。

Unity Shader 中,我们可以使用 tex2D 函数来进行纹理采样,它的参数有两个,一个是纹理对象,一个是 uv 坐标,它的返回值是一个 fixed4 类型的颜色值,表示了纹理上的纹素(texel)的颜色。例如:

fixed4 col = tex2D (_MainTex, i.uv);
  • 1

这段代码表示,从 _MainTex 这个纹理对象中,采样 i.uv 这个坐标处的颜色,赋值给 col 变量。

2. 纹理采样的优缺点

优点

可以大大提高物体表面的细节和真实感,使物体看起来更加美观和逼真。
可以减少顶点数据的传输和处理,提高渲染效率和性能。
可以灵活地调整和修改物体表面的颜色和效果,不需要重新编译着色器或者修改代码。

缺点

需要占用额外的内存空间和带宽,增加资源的加载和管理的开销。
需要考虑纹理的分辨率和质量,以及纹理坐标的映射和过滤,避免出现失真和锯齿的现象。
需要考虑纹理的环绕和过滤模式,以及纹理的压缩和优化,以适应不同的渲染需求和设备。

二、使用步骤

1. Shader 属性定义

// 定义属性
Properties
{
   _MainTex("MainTex",2D)="white"{}
   _Diffuse("Diffuse",Color)=(1,1,1,1) // 漫反射颜色属性,默认白色
   _Specular("Specular",Color)=(1,1,1,1) // 高光颜色属性,默认白色
   _Gloss("Gloss",Range(1,256))=5 // 高光反射系数
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这段代码定义了Shader的属性,其中:
_MainTex: 表示图片属性,它的默认值是白色,表示如果没有指定贴图,就使用白色填充。
_Diffuse: 表示漫反射颜色属性,使用RGBA格式表示颜色,默认为白色 (1, 1, 1, 1)。
_Specular: 表示高光颜色属性,同样使用RGBA格式表示颜色,默认为白色 (1, 1, 1, 1)。
_Gloss: 表示高光反射系数属性,使用Range声明范围为1到256,默认值为5。

2. SubShader 设置

SubShader
{
    Tags
    {
        "RenderType" = "Opaque" // 渲染类型为不透明
    }
    
    LOD 100 // 细节级别
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

SubShader 定义了一组渲染设置,包括标签和细节级别。在这里,我们将渲染类型标签设置为 “Opaque”,表示物体是不透明的。

3. 渲染 Pass

Pass
{
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag

    #include "UnityCG.cginc"
    #include "Lighting.cginc"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这里开始了渲染 Pass 部分。在这里,我们使用了 CGPROGRAM 指令来声明顶点着色器和片元着色器函数。#pragma vertex vert 和 #pragma fragment frag 分别指定了顶点着色器函数和片元着色器函数的名称。

然后,我们包含了 UnityCG.cginc 和 Lighting.cginc,它们提供了许多有用的函数和宏,用于简化编写 Shader。

4. 定义结构体和顶点着色器函数

// 定义结构体:从顶点到片元的数据传递
struct v2f
{
    float4 vertex:SV_POSITION; // 顶点位置
    fixed3 worldNormal:TEXCOORD0; // 世界空间法线
    fixed3 worldPos:TEXCOORD1;// 世界空间位置
    float2 uv:TEXCOORD2;
};

// 顶点着色器函数
v2f vert(appdata_base v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex); // 顶点位置变换到裁剪空间
    fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); // 世界空间法线
    o.worldNormal = worldNormal;

    //让外面的属性可以影响到uv
    //o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    //uv计算简化函数
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    o.worldPos=mul(unity_ObjectToWorld,v.vertex);
    return o;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

顶点着色器的输入是一个结构体 appdata_base ,它包含了顶点的位置和法线信息等。顶点着色器的输出是一个结构体 v2f ,它包含了顶点的裁剪空间位置和世界空间法线和位置信息等。

顶点着色器的主要逻辑是:

  1. 使用 UnityObjectToClipPos 函数,将顶点的位置从对象空间变换到裁剪空间,这是渲染管线的必要步骤。

  2. 使用 UnityObjectToWorldNormal 函数,将顶点的法线从对象空间变换到世界空间,这是为了计算光照效果所需的方向向量。

  3. 使用 TRANSFORM_TEX 函数,将顶点的贴图坐标根据 _MainTex_ST 的值进行缩放和偏移,这是为了让贴图的属性可以影响到贴图的显示。

  4. 使用 unity_ObjectToWorld 矩阵,将顶点的位置从对象空间变换到世界空间,这是为了计算光照效果所需的坐标系。

5. 片元着色器函数

// 片元着色器函数
fixed4 frag(v2f i) : SV_Target
{
    //获取环境光
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

    //纹理采样
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb;

    //漫反射
    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
    fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * max(0, dot(worldLightDir, i.worldNormal) * 0.5 + 0.5);

    // 高光反射
    // 计算视角方向
    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    // 计算半向量
    fixed3 halfDir = normalize(worldLightDir + viewDir);
    // 计算高光颜色   
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(i.worldNormal, halfDir)), _Gloss);

    // 组合最终颜色
    fixed3 color = diffuse + ambient + specular;
    return fixed4(color, 1); // 输出颜色
}


  • 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

片元着色器的输入是一个结构体 v2f ,它包含了顶点的裁剪空间位置和世界空间法线和位置信息。片元着色器的输出是一个 fixed4 类型的颜色值,它表示了片元的颜色。

片元着色器的主要逻辑是:

  1. 使用 UNITY_LIGHTMODEL_AMBIENT 宏,获取环境光的颜色,这是 Phong 光照模型的第一个分量。

  2. 使用 tex2D 函数,对 _MainTex 进行纹理采样,根据 i 的 uv 值获取对应的像素颜色,它是物体的表面颜色。

  3. 使用 UnityWorldSpaceLightDir 函数,获取光源的方向向量,这是为了计算漫反射和高光效果所需的角度。

  4. 使用 _LightColor0 和 _Diffuse 变量,获取光源的颜色和物体的漫反射颜色,然后使用 max 和 dot 函数,计算光源和法线的夹角的余弦值,然后乘以 0.5 并加上 0.5,这是为了将 [-1, 1] 的范围映射到 [0, 1] 的范围,这是 Phong 光照模型的第二个分量

  5. 使用 UnityWorldSpaceViewDir 函数,根据 i 的 worldPos 值获取视线的方向向量,这是为了计算高光效果所需的角度。

  6. 使用 normalize 函数,计算视线方向和光源方向的半向量,这是为了简化高光效果的计算。

  7. 使用 _LightColor0 和 _Specular 变量,获取光源的颜色和物体的高光颜色,然后使用 max 和 dot 函数,计算法线和半向量的夹角的余弦值,然后使用 pow 函数,根据 _Gloss 的值计算高光的强度,这是 Phong 光照模型的第三个分量。

  8. 将环境光、漫反射和高光的颜色相加,得到最终的光照颜色,作为片元着色器的输出。

三、效果

在这里插入图片描述

四、总结

纹理采样是从一张纹理图片中获取颜色值的过程,它需要指定一个纹理坐标(uv)和一个采样器(sampler)。采样器定义了纹理的过滤方式(filter)和寻址模式(wrap)。过滤方式决定了当纹理坐标不是整数时,如何插值得到颜色值,常见的有邻近点(point)、线性(linear)和各向异性(anisotropic)三种。寻址模式决定了当纹理坐标超出[0,1]范围时,如何处理边界情况,常见的有重复(repeat)、钳位(clamp)、边框(border)和镜像(mirror)四种。

使用场景

纹理采样的应用场景非常广泛,几乎所有的图形渲染都会用到纹理采样。例如:
基础颜色:我们可以使用一张彩色图片作为纹理,来为物体表面提供基础的颜色信息,从而使物体看起来更加丰富和多样。
法线贴图:我们可以使用一张法线图片作为纹理,来为物体表面提供法线信息,从而使物体看起来更加凹凸和立体。
高光贴图:我们可以使用一张灰度图片作为纹理,来为物体表面提供高光信息,从而使物体看起来更加光滑和反光。
遮罩贴图:我们可以使用一张黑白图片作为纹理,来为物体表面提供遮罩信息,从而使物体看起来更加复杂和多层次。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/115258
推荐阅读
相关标签
  

闽ICP备14008679号