之前总结过纹理管线的基础知识
【技术美术图形部分】纹理基础1.0-纹理管线_flashinggg的博客-CSDN博客
参考书籍
《Unity Shader 入门精要》冯乐乐,本篇博客对应书的7.1章节内容。
与书中不同的是,我的漫反射选择的是半兰伯特。
- 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"
- }
当然这并不符合常理(指砖块不会有这么大的高光效果),仅作为一个单张纹理应用的参考吧!
纹理不同于之前实现简单的光照,会用到更多的知识,在写shader的时候我也有一些疑惑的点,这里就来记录一下。
Properties中加入了_MainTex:
纹理来自导入的外部.jpg图片:
Pass中Cg代码中声明了与Properties语义块属性相匹配的变量:
- //properties
- fixed4 _Color; //用来控制物体的整体色调!
- fixed4 _Specular;
- float _Gloss;
- sampler2D _MainTex; //用这个纹理来表示漫反射颜色
- float4 _MainTex_ST; //固定用法:纹理名称_ST -> 纹理的缩放和平移属性值
其中,_MainTex类型——sampler2D。sampler2D是Cg变量类型,对应着ShaderLab属性类型的2D。
纹理名_ST——即代码中的"_MainTex_ST",是一种Unity的固定方式,以声明纹理的属性,ST是缩放(Scale)和平移(Translation)的缩写。
可以看到顶点着色器代码段中,求传递给片元着色器的顶点纹理坐标是经过缩放、平移变换后的结果,这个结果即可以老老实实用“缩放相乘、平移相加”的步骤:
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
也可以直接用内置宏TRANSFORM_TEX:
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
其实想把这部分重点放在定义的一个变量——albedo上面,其实在代码中我也有注释,尽量解释了每个涉及到albedo代码的意思。
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
albedo,材质的反射率,你可以理解为它被用来定义物体(也就是SingleTex这个材质)的基础固有色。上述代码段中我理解tex2D(_MainTex, i.uv).rgb是在进行纹理采样获取当前片元的纹理颜色,作为漫反射颜色。但我不理解为什么需要再乘以_Color.rgb值,要知道这个值在之前进行光照模型的时候是当作材质的漫反射颜色(一个单色),按理来说它应该直接被纹理颜色取代了才是。
这个问题很快就解决了。通过调试发现——留下_Color这个变量是为了控制物体在光照下的整体色调的。
也是可以控制高光的色彩,特别是以后面对一些金属,高光会呈现跟基础色不同的颜色。
计算漫反射将之前的_Color替换成了albedo都能理解,可为什么计算环境光颜色还需要乘一个albedo?为什么是:
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
而不是直接:
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
不✖albedo:
✖albedo:
好吧......光看这里差别不是很明显,但是还是有一点区别的!那就是“乘以albedo后整体变暗了”。
现在知道二者在效果上的差别了,那么下一步就是:为什么?
让我们回顾一下:环境光是为了在只能计算直接光照的标准光照模型中,近似模拟出间接光照的效果(这并不是真正的间接光照哈!),而且整个场景中的物体都会使用同一个环境光,它通常是一个全局变量。这个全局的环境光的颜色和强度信息如何获取?在shader代码中体现的就是UNITY_LIGHTMODEL_AMBIENT这个内置变量。
这就要涉及到,我们渲染光照目的是模拟人眼看到的颜色效果,而人眼看到的光实际上是经历了:光源发出-->在物体(材质)上进行一部分吸收,再反射-->传递到人眼,这一个过程,而非直接来自光源。
再结合之前的albedo定义——反射率,所以最终的返回值result中的ambient应该=环境光颜色 * 物体的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,效果如下:
这时代码部分result就要变成:
fixed3 resultColor = albedo * (diffuse + ambient + specular);
效果如下:
明显后者的效果相对更接近木材这种材质,但思考一下,如果换成金属,那毫无疑问就是前者更合适了。
因此,对于高光项,是否✖材质的基础色,需要结合材质的显示外观效果来综合考虑。
UnityShader学习(二)像素颜色和颜色向量相加相乘的理解_zsffff的博客-CSDN博客
可以看看上面这篇文章,总结得很好!
个人理解:相乘就是颜色需要在进入人眼之前先进行融合,例如环境光计算需要将环境光颜色✖物体基础色;相加就是进入人眼的时刻RGB三通道彼此不相影响,例如漫反射、高光和环境光三者是互不影响的。