《Shader入门精要》基础纹理

完整shader代码和示例图片可以看我的github:zaizai77/Shader-Learn: 实现一些书里讲到的shader

使用Unity内置函数

之前的例子中我们都是手动去获取光源方向和视角方向,使用:

使用normalize(_WorldSpace LightPos0.xyz)来得到光源方向(这种方法实际只适用于平行光)​

使用normalize(_WorldSpace CameraPos.xyz -i.worldPosition.xyz)来得到视角方向

但如果需要处理更复杂的光照类型,如点光源和聚光灯,我们计算光源方向的方向就是错误的,我们需要先在代码中判断光源类型,再计算光源信息

需要注意的是,这些函数都没有保证得到的方向矢量是单位矢量,因此,我们需要在使用前把它们归一化

基础纹理

使用纹理映射(texture mapping)技术,我们可以把一张图“黏”在模型表面,逐纹素(texel)​(纹素的名字是为了和像素进行区分)地控制模型的颜色。

在美术人员建模的时候,通常会在建模软件中利用纹理展开技术把纹理映射坐标(texture-mapping coordinates)存储在每个顶点上。纹理映射坐标定义了该顶点在纹理中对应的2D坐标。通常,这些坐标使用一个二维变量(u, v)来表示,其中u是横向坐标,而v是纵向坐标。因此,纹理映射坐标也被称为UV坐标。

单张纹理

我们通常会使用一张纹理来代替物体的漫反射颜色,在本节中,我们将学习如何在Unity Shader中使用单张纹理来作为模拟的颜色。

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

_MainTex_ST的名字不是任意起的。在Unity中,我们需要使用纹理名_ST的方式来声明某个纹理的属性。其中,ST是缩放(scale)和平移(translation)的缩写。_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  vert(a2v  v)  {
            v2f  o;
            o.pos  =  mul(UNITY_MATRIX_MVP,  v.vertex);
            o.worldNormal  =  UnityObjectToWorldNormal(v.normal);

            o.worldPos  =  mul(_Object2World,  v.vertex).xyz;

            o.uv  =  v.texcoord.xy  *  _MainTex_ST.xy  +  _MainTex_ST.zw;
            //  Or  just  call  the  built-in  function
            //        o.uv  =  TRANSFORM_TEX(v.texcoord,  _MainTex);

            return  o;
        }

在顶点着色器中,我们使用纹理的属性值_MainTex_ST来对顶点纹理坐标进行变换,得到最终的纹理坐标。计算过程是,首先使用缩放属性_MainTex_ST.xy对顶点纹理坐标进行缩放,然后再使用偏移属性_MainTex_ST.zw对结果进行偏移

Unity提供了一个内置宏TRANSFORM_TEX来帮我们计算上述过程。TRANSFORM_TEX是在UnityCG.cginc中定义的:

// Transforms 2D UV by scale/bias property

#define TRANSFORM_TEX(tex, name) (tex.xy * name##_ST.xy + name##_ST.zw)

它接受两个参数,第一个参数是顶点纹理坐标,第二个参数是纹理名,在它的实现中,将利用纹理名_ST的方式来计算变换后的纹理坐标。

我们还需要实现片元着色器,并在计算漫反射时使用纹理中的纹素值:

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

            //  Use  the  texture  to  sample  the  diffuse  color
            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);
        }

上面的代码首先计算了世界空间下的法线方向和光照方向。然后,使用CG的 tex2D 函数对纹理进行采样。它的第一个参数是需要被采样的纹理,第二个参数是一个float2类型的纹理坐标,它将返回计算得到的纹素值。我们使用采样结果和颜色属性_Color的乘积来作为材质的反射率albedo,并把它和环境光照相乘得到环境光部分。随后,我们使用albedo来计算漫反射光照的结果,并和环境光照、高光反射光照相加后返回。

纹理的属性

我们要为导入的纹理选择合适的类型,因为只有这样才能让Unity知道我们的意图,为Unity Shader传递正确的纹理,并在一些情况下可以让Unity对该纹理进行优化。

Wrap Mode。它决定了当纹理坐标超过[0, 1]范围后将会如何被平铺

Filter Mode属性,它决定了当纹理由于变换而产生拉伸时将会采用哪种滤波模式。Filter Mode支持3种模式:Point, Bilinear以及Trilinear。它们得到的图片滤波效果依次提升,但需要耗费的性能也依次增大。纹理滤波会影响放大或缩小纹理时得到的图片质量

纹理缩小的过程比放大更加复杂一些,此时原纹理中的多个像素将会对应一个目标像素。纹理缩放更加复杂的原因在于我们往往需要处理抗锯齿问题,一个最常使用的方法就是使用多级渐远纹理(mipmapping)技术

当我们在为不同平台发布游戏时,需要考虑目标平台的纹理尺寸和质量问题。Unity允许我们为不同目标平台选择不同的分辨率

如果导入的纹理大小超过了Max Texture Size中的设置值,那么Unity将会把该纹理缩放为这个最大分辨率。理想情况下,导入的纹理可以是非正方形的,但长宽的大小应该是2的幂,例如2、4、8、16、32、64等。如果使用了非2的幂大小(Non Power of Two, NPOT)的纹理,那么这些纹理往往会占用更多的内存空间,而且GPU读取该纹理的速度也会有所下降。有一些平台甚至不支持这种NPOT纹理,这时Unity在内部会把它缩放成最近的2的幂大小。出于性能和空间的考虑,我们应该尽量使用2的幂大小的纹理。

### Unity Shader 入门教程:纹理处理 #### 创建新的 Shader 文件 在 Unity 中创建一个新的 Shader 可以通过两种方式实现。一种是在菜单栏中选择 `Assets -> Create -> Shader`,另一种方法是从 Project 视图中右键点击并选择 `Create -> Shader`[^1]。 #### 纹理尺寸优化建议 为了确保最佳性能和资源利用效率,在 Unity 中使用的纹理图像应尽可能采用2的幂次方作为宽度和高度(例如 256×256 或者 512×512)。当使用非标准尺寸时,可能会导致额外的内存消耗,并且 GPU 处理速度会受到影响而变慢[^2]。 #### 表面着色器简介 对于初学者来说,表面着色器是一个很好的起点。这类着色器允许开发者定义材质属性如漫反射颜色、高光强度等参数。值得注意的是,虽然表面上看起来像是独立类型的着色程序,但实际上它们会被编译转换成传统的顶点/片段组合形式。下面给出了一段简单的代码示例来展示如何编写一个基本的表面着色器: ```csharp SubShader { Tags {"Queue"="Transparent"} CGPROGRAM #pragma surface surf Lambert struct Input { float4 color : COLOR; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = 1; } ENDCG } ``` 这段代码展示了如何设置一个具有透明队列标签并且应用兰伯特光照模型的简单着色器[^3]。 #### 减少过度绘制的方法 为了避免不必要的计算开销,可以通过调整场景中的对象顺序或者修改摄像机投影矩阵等方式减少视锥体内的可见几何图形数量。这样做能够显著提高渲染效率,尤其是在复杂环境中特别有用。此外,合理安排不同层次的对象遮挡关系同样有助于改善整体表现效果[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值