Unity Shader 入门到改行4——最简纹理采样

2020-12-25  本文已影响0人  太刀
运用你的想象力II

1. 纹理和纹素

2. uv 坐标

3. 采样单张纹理

在之前的 Blinn-Phong 中,我们使用如下的方式来计算物体表面的漫反射部分颜色:

// 半兰伯特计算漫反射
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * (dot(lightDir, worldNormal) * 0.5 + 0.5);

这样我们只能表示一个纯色物体,当我们需要表示物体表面各个位置不同颜色时,可以将不同位置处的颜色记录到纹理中,然后在渲染的时候读取这个纹理,把纹理中存储的颜色取出来,应用到漫反射计算公式中的 _DiffuseColor 即可。

现在我们先不考虑高光、环境光和自发光的情况,仅仅考虑如何将一个图片“贴”到物体的表面。
先上整体 Shader 代码 04_Texture.shader:

Shader "Shader_Examples/04_Texture"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque"}

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag                       

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {                           
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);// 顶点世界坐标
                o.uv = v.uv;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // 纹理采样
                fixed3 diffuse = tex2D(_MainTex, i.uv);
                return fixed4(diffuse, 1);
            }
            ENDCG
        }
    }
}

下面逐步分析这段代码的各个部分

3.1 纹理属性的声明

    Properties
    {
        _MainTex ("MainTex", 2D) = "white" {}
    }

3.2 顶点着色器输入和片段着色器输入

struct appdata
{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};

struct v2f
{                           
    float4 vertex : SV_POSITION;
    float2 uv : TEXCOORD0;
};

3.3 为属性声明相应的变量

sampler2D _MainTex;
float4 _MainTex_ST;

3.4 顶点着色器

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);// 顶点世界坐标
    o.uv = v.uv;
    return o;
}

3.5 片段着色器

fixed4 frag (v2f i) : SV_Target
{
    // 纹理采样
    fixed3 diffuse = tex2D(_MainTex, i.uv);
    return fixed4(diffuse, 1);
}

使用上面的 04_Texture.shader ,创建材质并指定给物体,然后照一张贴图指定给材质,可以得到如下的效果:


简单纹理采样

4. 采样贴图后加入环境光和高光

在将贴图“贴”到物体表面后,我们再给物体添加环境光和高光,关于高光和环境光,请看我之前的文章简单光照模型,添加环境光和高光后的 shader 代码如下:

Shader "Shader_Examples/04_BlinnPhong_Texture"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" {}
        _SpecularColor ("SpecularColor", Color) = (1,1,1,1)
        _Gloss ("Gloss", Range(8, 256)) = 20
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag       

            #include "Lighting.cginc"               

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _SpecularColor;
            float _Gloss;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);// 顶点世界坐标

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);// 法线变换
                o.worldPos = mul(unity_ObjectToWorld, o.vertex);
                o.uv = v.uv;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                float3 worldNormal = normalize(i.worldNormal);
                float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                float3 halfDir = normalize(lightDir + viewDir);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;      
                
                // 纹理采样
                fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * _LightColor0.rgb * (dot(lightDir, worldNormal) * 0.5 + 0.5);
                
                // 高光
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);
                return fixed4(diffuse + ambient + specular, 1);
            }
            ENDCG
        }
    }
}

渲染出的效果如下图:


纹理+环境光+高光

4. 纹理的其它应用

4.1 法线纹理(法线贴图)

法线贴图用来表现物体表面的凹凸效果,在贴图中存储物体在 "凹凸" 效果下的法线信息,渲染时将存储的法线信息采样出来,替代物体表面法线来进行光照计算,“还原”凹凸效果。具体法线贴图的应用,可以参考Unity Shader 入门到改行5——法线贴图

4.2 渐变纹理(渐变贴图)
渐变贴图是一种更灵活控制表面漫反射的方法,提供一张渐变贴图(通常是一维贴图),与传统的漫反射光照计算结合,可以提供各种有趣的渐变效果。


三种不同的渐变贴图控制漫反射效果

上图来自 UnityShader入门精要 中使用的3种渐变贴图和应用效果,对渐变贴图使用的核心代码是:

// Use the texture to sample the diffuse color
fixed halfLambert  = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
                
fixed3 diffuse = _LightColor0.rgb * diffuseColor;

渐变贴图通常为 一维 贴图,所以这里采样时用的 u 和 v 坐标一样,从代码可以看出,表面的法线与光线越夹角越小,漫反射颜色越接近渐变贴图的右边颜色,表面法线与光线夹角越大,漫反射颜色越接近渐变贴图的左边颜色。

4.3 遮罩纹理(遮罩贴图)

遮罩纹理是一种可以更加精准控制模型表面性质的方法,例如,我希望物体的某一部分不会产生高光,可以生成一张遮罩纹理,把物体在不产生高光部分的颜色值分量r设置为0并写到遮罩纹理中。渲染时采样遮罩纹理,使用 r 分量与计算出的 specular 相乘得到最终的 高光分量。使用这种方法可以灵活和精准地控制表面渲染效果。

上一篇 下一篇

猜你喜欢

热点阅读