【Unity Shader】纹理实践1.0:使用单张纹理

之前总结过纹理管线的基础知识

【技术美术图形部分】纹理基础1.0-纹理管线_flashinggg的博客-CSDN博客

参考书籍

《Unity Shader 入门精要》冯乐乐,本篇博客对应书的7.1章节内容。


 1 使用单张纹理的Shader完整代码

与书中不同的是,我的漫反射选择的是半兰伯特。

Shader "Unity Shaders Book/Chapter 7/Single Texture"
{
    Properties
    {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _Specular ("Specular", Color) = (1, 1, 1, 1)
        _Gloss ("Gloss", Range(8.0, 256)) = 20
        _MainTex ("Main Tex", 2D) = "white" {}
    }
    SubShader
    {
        Pass {
            Tags { "LightMode" = "ForwardBase" }

            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            //properties
            fixed4 _Color;  //用来控制物体的整体色调!
            fixed4 _Specular;
            float _Gloss;
            sampler2D _MainTex;  //用这个纹理来表示漫反射颜色
            float4 _MainTex_ST;  //固定用法:纹理名称_ST -> 纹理的缩放和平移属性值  

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;  //将第一组纹理坐标储存在texture里
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;  //变量uv便于片元着色器进行纹理采样
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                //变换后的顶点纹理坐标
                //_MainTex_ST.xy是缩放,.zw是平移
                o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                //o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);  //也有Unity内置宏处理纹理变换过程

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldlightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(viewDir + worldlightDir);
                fixed halfLambert = dot(worldNormal, worldlightDir) * 0.5 +0.5;

                //albedo - 材质的基础固有色,可以是纹理贴图/纯色的单色
                //纹理颜色 和 控制色调的_Color 混合 -> 即相乘
                fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

                //环境光
                //这里的ambient也是经过:light -> 物体 -> 反射出环境光,因此需要用光的颜色✖物体的albedo
                fixed3 ambient  = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                //漫反射
                //用了半兰伯特
                fixed3 diffuse = _LightColor0.rgb * albedo * halfLambert;
                
                //高光
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);

                return fixed4(ambient + diffuse + specular, 1.0);
            }
            ENDCG
        }
    }
    FallBack  "Specular"
}

Game视图的动态效果

当然这并不符合常理(指砖块不会有这么大的高光效果),仅作为一个单张纹理应用的参考吧!

2 纹理相关的shader代码解析

纹理不同于之前实现简单的光照,会用到更多的知识,在写shader的时候我也有一些疑惑的点,这里就来记录一下。

2.1 用纹理代替_Color作为漫反射颜色

Properties中加入了_MainTex:

纹理来自导入的外部.jpg图片:

2.2 sampler2D与2D

Pass中Cg代码中声明了与Properties语义块属性相匹配的变量:

//properties
fixed4 _Color;  //用来控制物体的整体色调!
fixed4 _Specular;
float _Gloss;
sampler2D _MainTex;  //用这个纹理来表示漫反射颜色
float4 _MainTex_ST;  //固定用法:纹理名称_ST -> 纹理的缩放和平移属性值  

其中,_MainTex类型——sampler2D。sampler2DCg变量类型,对应着ShaderLab属性类型2D

2.3 _MainTex_ST -> 纹理名_ST

纹理名_ST——即代码中的"_MainTex_ST",是一种Unity的固定方式,以声明纹理的属性,ST是缩放(Scale)和平移(Translation)的缩写。

2.4 Unity内置宏处理纹理变换

可以看到顶点着色器代码段中,求传递给片元着色器的顶点纹理坐标是经过缩放、平移变换后的结果,这个结果即可以老老实实用“缩放相乘、平移相加”的步骤:

o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

也可以直接用内置宏TRANSFORM_TEX

o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

3 光照相关的shader代码解析

其实想把这部分重点放在定义的一个变量——albedo上面,其实在代码中我也有注释,尽量解释了每个涉及到albedo代码的意思。

3.1 albedo与_Color 

fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

3.1.1 _Color的作用是?

albedo,材质的反射率,你可以理解为它被用来定义物体(也就是SingleTex这个材质)的基础固有色。上述代码段中我理解tex2D(_MainTex, i.uv).rgb是在进行纹理采样获取当前片元的纹理颜色,作为漫反射颜色。但我不理解为什么需要再乘以_Color.rgb值,要知道这个值在之前进行光照模型的时候是当作材质的漫反射颜色(一个单色),按理来说它应该直接被纹理颜色取代了才是。

这个问题很快就解决了。通过调试发现——留下_Color这个变量是为了控制物体在光照下的整体色调的。 

3.1.2 类比_Specular的作用?

也是可以控制高光的色彩,特别是以后面对一些金属,高光会呈现跟基础色不同的颜色。

3.2 环境光计算与albedo 

计算漫反射将之前的_Color替换成了albedo都能理解,可为什么计算环境光颜色还需要乘一个albedo?为什么是:

fixed3 ambient  = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

而不是直接:

fixed3 ambient  = UNITY_LIGHTMODEL_AMBIENT.xyz;

3.2.1 albedo加与不加的效果对比

不✖albedo:

✖albedo:

好吧......光看这里差别不是很明显,但是还是有一点区别的!那就是“乘以albedo后整体变暗了”。

现在知道二者在效果上的差别了,那么下一步就是:为什么?

3.2.2 为什么要✖albedo?

  • 问题1:环境光这一项是为了干什么?

让我们回顾一下:环境光是为了在只能计算直接光照的标准光照模型中,近似模拟出间接光照的效果(这并不是真正的间接光照哈!),而且整个场景中的物体都会使用同一个环境光,它通常是一个全局变量。这个全局的环境光的颜色和强度信息如何获取?在shader代码中体现的就是UNITY_LIGHTMODEL_AMBIENT这个内置变量。

  • 问题2:光源经历了什么到达人眼?

这就要涉及到,我们渲染光照目的是模拟人眼看到的颜色效果,而人眼看到的光实际上是经历了:光源发出-->在物体(材质)上进行一部分吸收,再反射-->传递到人眼,这一个过程,而非直接来自光源。

再结合之前的albedo定义——反射率,所以最终的返回值result中的ambient应该=环境光颜色 * 物体的albedo,这才是最终人眼看到的颜色。

3.3 高光反射计算与albedo

3.3.1 不考虑albedo的效果

书中的例子并没有给高光项specular也✖albedo,那么让我们修改一下代码,书中的效果其实是:

//环境光项
fixed3 ambient  = UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射项
fixed3 diffuse = _LightColor0.rgb * halfLambert;
//高光项
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);
//结果
fixed3 resultColor = albedo * (diffuse + ambient ) + specular;
return fixed4(resultColor, 1.0);

为了与木材粗糙的外观吻合,_Gloss取最小值8,效果如下:

3.3.2 考虑albedo的效果

这时代码部分result就要变成:

fixed3 resultColor = albedo * (diffuse + ambient + specular);

效果如下:

明显后者的效果相对更接近木材这种材质,但思考一下,如果换成金属,那毫无疑问就是前者更合适了。

因此,对于高光项,是否✖材质的基础色,需要结合材质的显示外观效果来综合考虑。

4 Unity Shader中颜色相加和相乘  

UnityShader学习(二)像素颜色和颜色向量相加相乘的理解_zsffff的博客-CSDN博客

可以看看上面这篇文章,总结得很好!

个人理解:相乘就是颜色需要在进入人眼之前先进行融合,例如环境光计算需要将环境光颜色✖物体基础色;相加就是进入人眼的时刻RGB三通道彼此不相影响,例如漫反射、高光和环境光三者是互不影响的。

猜你喜欢

转载自blog.csdn.net/qq_41835314/article/details/127036922