关于Shader中贴图的基本使用
伊底_1D
2022年06月29日 20:24
收录于文集
共16篇

前言        

        从本节开始,文章的内容开始逐渐转换为“技术分享”风格,即相较于之前的几个“入门教程”,一些专业术语直接拿来用的情况可能会在以后越来越多,且会开始深挖一些技术的原理。不过对于本系列里第一次涉及到的概念,我(如果想起来了- -···)也会简单讲讲(比如这次的UV映射技术)。

        当然,在内容合理且有价值的前提下,评论区也没这么多规矩。如果想补充或讨论照例十分欢迎。

        本次目标——知道怎么把一张图贴到模型上——>成功。那么开冲。

一、UV映射技术

        将一张纹理(贴图)点对点的贴到模型上需要用到的便是UV映射技术。UV实际上就是一组二维坐标,这些坐标存储在模型的每个顶点中。

1-1

        对于上面这张图的右侧,捏过模型的同学应该不陌生,并且经常顺口把这种网格图叫UV。实际上这个网格图就是把每个模型顶点按UV坐标放到二维坐标系中,然后再按原本的布线关系把这些点连起来形成的定位图。(其实也就是很多教程喜欢用的一个比喻,把一个立体的纸壳子展平的过程)

        这么搞的意义自然是因为贴图一般都是2D的,在这个定位图的基础上才能让模型表面的每一点与贴图上的每个像素一一对应。更进一步说,在制作流程上也是先有UV(展开),后在此基础上用SP,BP之类的软件画的贴图。(所以才会出现下面这种看上去扭曲严重或者一块一块的图像资源,因为它是在UV的基础上画出来的)

1-2

二、光栅化与线性插值

        UV信息存储在模型的每个顶点里,但根据隔壁​第二节给出的模板里的注释,像素shader中也可能会用到UV,而这个阶段的处理是逐像素的,也就是像素shader里参与计算的UV一般比模型本身的UV多很多。

        只要不是模型离得太远或是角度刁钻、又或者ZB里那种几千万顶点的超高模,模型在屏幕上显示的像素数一般都比顶点数要多得多,也就是像素shader在同一时间内跑的次数比顶点shader多,进而得出上面“多出很多UV”的结论。

        这些多出来的UV是靠光栅化过程中的线性插值自动算出来的。(这个过程夹在顶点shader和像素shader中间,无法编程)其实不光是UV,顶点到像素的结构体中所有的数据都会经过这一步插值处理。关于这个插值的过程可以参考一下【这个​】,还是挺直白易懂的。

三、不要管这么多,用Shader把图贴上去吧

        其实上面这些原理不懂也没关系- -贴个图而已,写shader天天都要贴图采样- -下面几行代码复制粘贴就完事。(需要内置管线版本的参照隔壁​第二节后半进行修改)

代码块
JavaScript
自动换行
复制代码
//URP
Shader "Texture"
{
    Properties
    {
        [Header(Texture)]
        [NoScaleOffset] _MainTex ("主贴图", 2D) = "white" {}

        [Space(20)]
        [Header(Switch)]
        [Toggle] _UV2 ("使用2U", int) = 0   //因为示例模型的头发出于一些效果上的目的用了2U,所以加了这个开关

        [Space(20)]
        [Header(Color)]
        [HDR] _Color ("色调", color) = (1.0, 1.0, 1.0, 1.0)
    }

    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "RenderType" = "Opaque"
            "Queue" = "Geometry"
        }

        Pass
        {
            Cull back
            ZWrite on

            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            CBUFFER_START(UnityPerMaterial)
                //贴图
                sampler2D _MainTex;

                //开关
                uint _UV2;

                //颜色
                half3 _Color;
            CBUFFER_END

            struct a2v
            {
                half4 posOS    : POSITION;
                float2 uv1       : TEXCOORD0;
                float2 uv2       : TEXCOORD1;
            };

            struct v2f
            {
                half4 posCS    : SV_POSITION;
                float2 uv       : TEXCOORD0;
            };

            v2f vert (a2v i)
            {
                v2f o;

                //坐标
                o.posCS = TransformObjectToHClip(i.posOS.xyz);

                //UV
                o.uv = (1 - _UV2) * i.uv1 + _UV2 * i.uv2;

                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                //采样
                half4 var_MainTex = tex2D(_MainTex, i.uv);
                
                //混合
                half3 finalCol = _Color * var_MainTex.rgb;

                return half4(finalCol, 1.0);
            }

            ENDHLSL
        }
    }
    FallBack "Hidden/Universal Render Pipeline/FallbackError"
}
复制成功

        上述代码的功能就是贴张图并加了个颜色的相乘控制整体色调(PS里的正片叠底就是这个算法),最终效果如下。(不要奇怪为什么地上没有投影- -因为投影要出来也是要在shader里写的。以后有机会会说)

3-1

        一般调色方面用到的也就是乘个颜色,以实现在不同环境下的色彩倾向变化。至于色相对比饱和度之类的经典套餐,一般要不直接改贴图要不用后处理(虽然后处理本质就是个全屏shader)。材质shader里虽然也能写,但这几个常见的效果在算法上还是有点复杂的(相对于正片叠底的简单一乘),而且这种需求少,所以一般不这么干。

        最后给大家看看单独把模型的UV放到红绿通道里输出的效果。这种单独把某个变量提前return的方式也是shader常用的debug方式。(毕竟不像一般的程序只是输出某个值或某些值,shader输出的动不动就是几千上万个像素的颜色)

3-2