-
2020-06-13 15:18:56
基础知识
-
纹理的另一种场景的应用就是凹凸映射。凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是"凹凸不平"的,可以从模型的轮廓处看出“破绽”。
-
有两种主要的方法可以用来进行凹凸映射:
- 使用一张高度纹理来模拟表面位移,然后得到一个修改后的法线,这种被称为高度映射;
- 使用一张法线纹理来直接存储表面法线,这种被称为法线映射;
-
注意:
- 凹凸映射,从纹理中得到的法线,只会影响光照模型。
- float4 tangent:TANGENT; // 切线,float4 类型,用tangent.w 分量来决定切线空间中第三个轴——副切线y的方向。
-
采样获取法线
fixed3 bump = UnpackNormal(tex2D(_BumpMap, v.uv.zw)); //上计算等价于 fixed4 packedNormal = tex2D(_BumpMap, v.uv.zw); fixed3 tangentNormal; tangentNormal.xy = (packedNormal.xy * 2 - 1) *_BumpScale; tangentNormal.z = sqrt(1-saturate(dot(tangentNormal.xy, tangentNormal.xy)));
原因:纹理坐标中只记录 xy,z 需要计算得到。而且 xy 是经过映射的 pixed = (normal + 1) / 2,需要首先进行反映射,然后求 z。
- 使用 UnpackNormal 函数对法线进行采样和解码时,需要把纹理格式标识为 Normal map。
- 在 Unity5.x 中,所有的内置 Unity shader 都是用世界空间来进行光照计算。
高度纹理
- 高度图中存储的是强度值,它用来表示模型表面局部的海拔高度。颜色越浅表明该位置的表面越向外凸起,颜色越深表明该位置越向里凹;这种方法的好处是直观,但缺点是计算更加复杂,在实时计算时不能直接得到表面法线,而是需要由像素的灰度值来计算而得,因此需要消耗更多的性能。
- 高度图通常会和法线映射一起使用,用于给出表面凹凸的额外信息,也就是说,我们通常会使用法线映射来修改光照。
Unity 中的法线纹理类型
- 当使用包含了法线映射的内置的UnityShader时,必须把使用的法线纹理标识成Normalmap才能有正确结果。这是因为UnityShader都是用来内置的UnpackNormal函数来采样法线方向。
- 当把纹理类型设置为Normalmap时,Unity根据不同平台进行压缩,再通过UnpackNormal函数来针对不同的压缩格式对法线纹理进行正确的采样。可以通过源码查看
inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal) { fixed3 normal; normal.xy = packednormal.wy * 2 - 1; normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy))); return normal; } // Unpack normal as DXT5nm (1, y, 1, x) or BC5 (x, y, 0, 1) // Note neutral texture like "bump" is (0, 0, 1, 1) to work with both plain RGB normal and DXT5nm/BC5 fixed3 UnpackNormalmapRGorAG(fixed4 packednormal) { // This do the trick packednormal.x *= packednormal.w; fixed3 normal; normal.xy = packednormal.xy * 2 - 1; normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy))); return normal; } inline fixed3 UnpackNormal(fixed4 packednormal) { #if defined(UNITY_NO_DXT5nm) return packednormal.xyz * 2 - 1; #else return UnpackNormalmapRGorAG(packednormal); #endif }
从代码中可以看到,在 DXT5nm 格式的法线纹理中,纹素为 (1, y, 1, x);在 BC5 格式中则为(x, y, 0, 1) 。
- 法线纹理坐标中只记录xy,因为它只有两个通道是真正必不可少的,第三个通道的值可以用另外两个推导出来(法线是单位向量,并且切线空间下的法线方向的z分量始终为证),使用这种压缩可以减少法线纹理占用的存储空间。
- 当把纹理类型设置为 Normal map 后,还有一个复选框 Create from Grayscale,这个复选框作用是从高度图中生成法线纹理。高度图本身记录的是相对高度,是一张灰度图。勾选复选框后,就可以把该纹理和切线空间下的法线纹理同等对待了。
勾选复选框后- Bumpiness 用于控制凹凸程度
- Filtering 决定使用哪种方式来计算凹凸程度
法线纹理
-
法线纹理中存储的就是表面的法线方向。由于法线方向的分量范围在[-1,1],而像素的分量范围为[0, 1],因此需要做一个映射,通常使用的映射就是:
pixed = (normal + 1) / 2
这就要求,在 Shader 中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,已得到原先的法线方向。
normal = pixed * 2 - 1 -
模型空间的法线纹理和切线空间的法线纹理
- 模型空间的法线纹理的优点:
- 实现简单,更加直观。我们甚至不需要模型原始的法线和切线等信息,计算更少。生成它也非常简单,而如果要生成切线空间下的法线纹理,由于模型的切线一般是和UV方向相同,因此想要得到效果较好的法线映射就要求纹理映射也是连续的。
- 在纹理坐标的缝合处和尖锐的边角部分,可见的突变(缝隙)较少,即可以提供平滑的边界。这是因为模型空间下的法线纹理存储是同一坐标系下的法线信息,因此在边界处通过插值得到的法线可以平滑变换。而切线空间下的法线纹理中的法线信息是依靠纹理坐标系的方向得到的结果,可能会在边缘处或尖锐的部分造成更多可见的缝隙。
- 切线空间的法线纹理的优点:
- 自由度很高。模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它时的那个模型,而应用到其他模型上效果就完全错误 。而切线空间下的法线纹理记录的是相对法线信息,这意味着,即便把该纹理应用到一个完全不同的网格上,也可以得到一个合理的效果。
- 可以进行UV动画。比如,我们可以移动一个纹理的UV坐标来实现一个凹凸移动效果。
- 可以重用法线纹理。比如,一个砖块,我们可以使用一张法线纹理就可以用到所有的6个面。
- 可压缩。由于切线空间下的法线纹理中发现的Z方向总是正方向,因此我们可以仅存储XY方向,而推导得到Z方向。而模型空间下的法线纹理由于每个方向都是可能的,因此必须存储3个方向的值,不可压缩。
-
计算光照模型,需要统一各个方向矢量所在的坐标空间。由于法线纹理中存储的法线是切线空间下的方向,所以有两种选择:
- 在切线空间进行光照计算,把视角方向、光照方向切换到切线空间下;
- 在世界空间进行光照计算,把采样得到的法线方向变换到世界空间下,在和世界空间下的光照方向和视角方向进行计算;
- 比较
- 从效率上说,第一种方法优于第二种方法,因为可以在顶点着色器就完成对光照和视角方向的变换,而第二种方法由于先对法线纹理进行采样,所以变换过程必须在片元着色器中实现,这意味着需要在片元着色器中进行一次矩阵操作。
- 从通用性来说,第二种方法优于第一种方法,还需要在世界空间进行其他计算。
-
在切线空间下计算
- 思路
在片元着色器中通过纹理采样得到切线空间下的法线,然后在与切线空间下的视角方向、光照方向等进行计算,得到最终的光照结果。 - 实现
首先需要在顶点着色器中把视角方向和光照方向从模型空间变换到切线空间中,即需要知道从模型空间到切线空间的变换矩阵。这个矩阵的逆矩阵,从切线空间到模型空间的变换矩阵,在顶点着色器中按切线(x轴)、副切线(y轴)、法线(z轴)的顺序按列排列即可得到。如果一个变换中仅存在平移和旋转变换,那么这个变换的逆矩阵就等于它的转置矩阵,而从切线空间到模型空间的变换正式符合这样要求的变换。因此,,从模型空间到切线空间的变换矩阵就是从切线空间到模型空间的变换矩阵的转置矩阵,把切线(x轴)、副切线(y轴)、法线(z轴)的顺序按行排列即可得到。
- 思路
Shader "Custom/s7_2" { Properties { // 纹理贴图代替漫反射 _Color("Color",color)=(1,1,1,1) _MainTex("Main Tex",2D)="white"{} // 高光反射 _Specular("Specular",color)=(1,1,1,1) _Gloss("Gloss",Range(0,20))=20 // 凹凸映射 _BumpMap("Bump Map",2D)="bump"{} // bump是Unity内置的法线纹理,当没有提供任何法线纹理时,bump对应了模型自带的法线信息 _BumpScale("Bump Scale",Range(0,1))=0.5 // 控制凹凸程度,为0时,意味着该法线纹理不会对光照产生任何影响 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 Pass { Tags {"LightMode"="ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Specular; float _Gloss; sampler2D _BumpMap; float4 _BumpMap_ST; float _BumpScale; /* * 切线空间下计算,需要把视角方向、光照方向变换到切线空间下 * 已知模型空间下视角方向、光照方向, * 已知切线空间的三个轴在模型空间的表示x轴(切线)、z轴(法线),可以叉乘求得y轴(副切线) * 从模型空间变换到切线空间,躺着,即按行展开 */ struct a2v { float4 position:POSITION; float3 normal:NORMAL; // 法线 float4 tangent:TANGENT; // 切线 float3 texcoord:TEXCOORD0; // 第一组纹理坐标 }; struct v2f { float4 pos:SV_POSITION; // 顶点坐标变换 float3 tangLightDir:TEXCOORD0; // 光照方向,从模型空间变换到切线空间 float3 tangViewDir:TEXCOORD1; // 视角方向 float4 uv:TEXCOORD2; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.position); o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap); // 法线,切线得到y float3 y = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w; // 构建从模型空间到切线空间的矩阵 fixed3x3 trans = fixed3x3(v.tangent.xyz, y, v.normal); // 空间变换 o.tangLightDir = mul(trans, ObjSpaceLightDir(v.position)); o.tangViewDir = mul(trans, ObjSpaceViewDir(v.position)); return o; } fixed4 frag(v2f v):SV_Target { // 归一化 fixed3 tangLightDir = normalize(v.tangLightDir); fixed3 tangViewDir = normalize(v.tangViewDir); // 纹理采样 fixed4 packedNormal = tex2D(_BumpMap, v.uv.zw); fixed3 tangentNormal; // 如果纹理图不是 normal map // tangentNormal.xy = (packedNormal.xy * 2 - 1) *_BumpScale; // tangentNormal.z = sqrt(1-saturate(dot(tangentNormal.xy, tangentNormal.xy))); // 或者标识为 Normal map tangentNormal = UnpackNormal(packedNormal); // 如果没有 _BumpScale, 下面两步可以不执行 tangentNormal.xy *= _BumpScale; tangentNormal.z = sqrt(1-saturate(dot(tangentNormal.xy, tangentNormal.xy))); // 反射率 fixed3 albedo = tex2D(_MainTex, v.uv).rgb * _Color.rgb; // 环境光 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; // 漫反射 fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(tangentNormal, tangLightDir)); // 高光反射 fixed3 halfDir = normalize(tangViewDir + tangLightDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, halfDir)), _Gloss); return fixed4(ambient + diffuse + specular, 1); } ENDCG } } FallBack "Diffuse" }
- 世界空间下计算光照模型,需要在片元着色器中把法线方向从切线空间变换到世界空间下。基本思路:在顶点着色器中计算从切线空间到世界空间的变化矩阵,并把他们传递给片元着色器。变换矩阵的计算可以由顶点的切线、副切线和法线在世界空间下的表示来得到。
Shader "Custom/s7_2_w" { Properties { _Color("Color",color)=(1,1,1,1) _MainTex("Main Tex",2D)="white"{} _BumpMap("Bump Map",2D)="bump"{} _BumpScale("Bump Scale", Range(0, 1)) = 1 _Specular("Specular",color)=(1,1,1,1) _Gloss("Gloss",Range(8,255))=50 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 Pass { Tags{"LightMode"="ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _BumpMap; float4 _BumpMap_ST; float _BumpScale; fixed4 _Specular; float _Gloss; /* * 切线空间中的法线映射 * 在世界空间中计算光照模型,视角方向、光照方向易得 * 需要把采样得到的切线空间中的法线变换到世界空间,已知模型空间下切线空间的 x轴(切线)、z轴(法线),可得 y 轴(副切线),将它们变换到世界空间下 * 即可得到在世界空间中切线空间的3个轴,从切线空间到法线空间,需要站着,即按列展开 */ struct a2v { float4 position:POSITION; float3 normal:NORMAL; float4 tangent:TANGENT; float3 texcoord:TEXCOORD0; }; struct v2f { float4 pos:SV_POSITION; float4 uv:TEXCOORD0; float4 tangx:TEXCOORD2; float4 tangy:TEXCOORD3; float4 tangz:TEXCOORD4; }; // 从切线空间变换到世界空间,已知在模型空间中的 x轴 切线,z轴 法线 v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.position); o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); // 代替漫反射 o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap); // 法线 float3 worldPos = mul(unity_ObjectToWorld, v.position).xyz; fixed3 worldtang = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldnormal = UnityObjectToWorldNormal(v.normal); fixed3 worldbinormal = cross(worldnormal, worldtang) * v.tangent.w;// w分量控制方向 // 充分利用插值寄存器的存储空间,把世界空间下的顶点位置存储在变量的 w 分量中 o.tangx = float4(worldtang.x, worldbinormal.x, worldnormal.x, worldPos.x); o.tangy = float4(worldtang.y, worldbinormal.y, worldnormal.y, worldPos.y); o.tangz = float4(worldtang.z, worldbinormal.z, worldnormal.z, worldPos.z); return o; } fixed4 frag(v2f v):SV_Target { float3 worldPos = float3(v.tangx.w, v.tangy.w, v.tangz.w); // 光照方向 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos)); // 视角方向 fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); // 计算法线,从切线空间变换到世界空间 fixed3 bump = UnpackNormal(tex2D(_BumpMap, v.uv.zw)); bump.xy *= _BumpScale; bump.z = sqrt(1 - saturate(dot(bump.xy, bump.xy))); bump = normalize(half3(dot(v.tangx.xyz, bump), dot(v.tangy, bump), dot(v.tangz, bump))); // 替代漫反射的纹理采样 fixed3 albedo = tex2D(_MainTex, v.uv).rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(bump, worldLightDir)); // 高光反射 fixed3 halfDir = normalize(worldLightDir + worldViewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(bump, halfDir)), _Gloss); return fixed4(ambient + diffuse + specular, 1); } ENDCG } } FallBack "Diffuse" }
更多相关内容 -
-
Shader视差贴图
2021-04-30 20:11:11视差贴图相关理论介绍 粗略的视差效果: 本文就不陈述视差贴图相关描述了, 此处只记录说明一下两点 (1) 在求uv的偏移量的时候 :在切线空间下的 viewDir.xy / viewDir.z , 这里为...AC的高度为:1 相似三角形:粗略的视差效果:
本文就不陈述视差贴图相关描述了, 此处只记录说明一下两点
(1) 在求uv的偏移量的时候 :在切线空间下的 viewDir.xy / viewDir.z , 这里为什么要除以 .z
(2)以及直接使用上面链接中的“改进3”的 RayMarching (光线步进)实现精确的视差效果
(一)viewDir.xy / viewDir.z 的解释:
(1)求出BC: 最大的UV偏移值
AE, AG: 归一化后的 viewDir
AC的高度为:1
相似三角形: EF/ AF = BC / AC
即: viewDir.xy / viewDir.z = BC / 1根据以上条件: BC = (viewDir.xy / View.z) * 1
因此 ,uv的偏移量 uvOffset = BC
uvOffset乘以很小的偏移系数,误差就会非常小。
(为防止.z太小,造成误差太大 通常: view.xy / (view.z + 0.42) )(2)精确的偏移值:BQ
人眼看到E点就被挡住了 B点的UV + BQ 才是 在Q点采样得到的高度 QE
(二)RayMatching 光线步进求相对精确的交点
Shader "Unlit/Parallax" { Properties { _MainTex ("Texture", 2D) = "white" {} _ParallaxMap("Parallax",2D) = "white"{} _HeightScale("HeightScale",Range(0,0.1)) = 1 _BumpMap("BumpMap",2D) = "Bump"{} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" #define PARALLAX_RAYMARCHING_STEPS 150 #define PARALLAX_RAYMARCHING_BINARY_SEARCH_STEPS 10 struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float4 tangent : TANGENT; float3 normal : NORMAL; }; struct v2f { float2 uv : TEXCOORD0; float4 pos : SV_POSITION; float4 T2W0 : TEXCOORD1; float4 T2W1 : TEXCOORD2; float4 T2W2 : TEXCOORD3; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _ParallaxMap; float _HeightScale; sampler2D _BumpMap; // unity 的内置函数 //ParallaxOffset( ) //-----------------RayMatching 封装的相关代码----------------------------- // 在函数中用到多次 tex2D采样 和 for循环,代价相对较大。。。。。。 float GetParallaxHeight (float2 uv) { return tex2D(_ParallaxMap, uv).r ; } float2 ParallaxRaymarching (float2 uv, float2 viewDir) { #if !defined(PARALLAX_RAYMARCHING_STEPS) #define PARALLAX_RAYMARCHING_STEPS 5 #endif float2 uvOffset = 0; float stepSize = 1.0 / PARALLAX_RAYMARCHING_STEPS; float2 uvDelta = viewDir * stepSize; float stepHeight = 1; float surfaceHeight = GetParallaxHeight(uv); for (int i = 0; i < PARALLAX_RAYMARCHING_STEPS && stepHeight > surfaceHeight; ++i) { uvOffset -= uvDelta; stepHeight -= stepSize; surfaceHeight = GetParallaxHeight(uv + uvOffset); } #if !defined(PARALLAX_RAYMARCHING_BINARY_SEARCH_STEPS) #define PARALLAX_RAYMARCHING_BINARY_SEARCH_STEPS 2 #endif for (int i = 0; i < PARALLAX_RAYMARCHING_BINARY_SEARCH_STEPS; i++) { uvDelta *= 0.5; stepSize *= 0.5; if (stepHeight < surfaceHeight) { uvOffset += uvDelta; stepHeight += stepSize; }else{ uvOffset -= uvDelta; stepHeight -= stepSize; } surfaceHeight = GetParallaxHeight(uv + uvOffset); } return uvOffset; } float2 DoParallaxMap(float3 viewDir,float2 uv){ viewDir = normalize(viewDir); viewDir.xy /=(viewDir.z + 0.41); viewDir.xy *= _HeightScale; uv += ParallaxRaymarching(uv.xy,viewDir.xy); return uv; } //-----------------RayMatching end------------------------------ v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); float3 normal =UnityObjectToWorldNormal(v.normal); float3 tangent = UnityObjectToWorldDir(v.tangent); float3 binormal = cross(normal,tangent) * v.tangent.w; float3 worldPos = mul(unity_ObjectToWorld,v.vertex); o.T2W0 = float4(tangent.x,binormal.x,normal.x,worldPos.x); o.T2W1 = float4(tangent.y,binormal.y,normal.y,worldPos.y); o.T2W2 = float4(tangent.z,binormal.z,normal.z,worldPos.z); return o; } // 初始通用版本 fixed4 frag (v2f i) : SV_Target { float3 worldPos = float3(i.T2W0.w,i.T2W1.w,i.T2W2.w); float3 normal = UnpackNormal(tex2D(_BumpMap,i.uv)); normal = float3(dot(i.T2W0.xyz,normal),dot(i.T2W1.xyz,normal),dot(i.T2W2.xyz,normal)); float3 view_ws =normalize(UnityWorldSpaceViewDir(worldPos)); float3 t = float3(i.T2W0.x,i.T2W1.x,i.T2W2.x); float3 b = float3(i.T2W0.y,i.T2W1.y,i.T2W2.y); float3 n = float3(i.T2W0.z,i.T2W1.z,i.T2W2.z); float3 view_ts = float3(dot(t,view_ws),dot(b,view_ws),dot(n,view_ws)); 在视差贴图中 R,G,B, 三个分量的值相等,所以随便用一个就好 float height = tex2D(_ParallaxMap,i.uv).r; float类型的 ,如 1.0f , 不要写成1, 不然会被当成int处理,舍弃小数,造成精度不准; /// 0.5分界线高度图 来达到上下各异的视差 height -= 0.5f; // 此处的 .xy / .z 是利用相似三角形,求出精确的 xy 的偏移量 float2 offsetUV = height*( view_ts.xy/ (view_ts.z + 0.42)) * _HeightScale; i.uv += offsetUV; float4 col = tex2D(_MainTex,i.uv); return col; } ENDCG } } }
-
【Unity Shader】(6)凹凸映射 实现材质的 法线贴图 和 高度图(Normal Map And Height Map)
2022-04-09 15:39:43在Unity Shader中用法线贴图和高度图来实现凹凸映射 1、凹凸映射概念 凹凸映射,在不改变顶点位置的前提下,修改模型表面的法线方向,为模型提供更多的细节。 2、凹凸映射的 2 种方法 高度纹理(Height Map) ...在Unity Shader中用法线贴图和高度图来实现凹凸映射
1、凹凸映射概念
凹凸映射,在不改变顶点位置的前提下,修改模型表面的法线方向,为模型提供更多的细节。
2、凹凸映射的 2 种方法
使用一张
高度纹理
来模拟表面位移(Displacement),然后得到一个修改后的法线值。此方法也叫做高度映射(Height Mapping)
。
颜色越浅,越向外凸;颜色越深,越向内凹。能明确表面的凹凸信息
,缺点是计算复杂。
使用一张
法线纹理
来直接存储表面法线。此方法也叫做法线映射(Normal Mapping)
。用于存储表面的法线向量
,法线向量的取值范围为 [ -1 , 1] 。但是像素分量的取值范围是 [ 0 , 1],因此需要进行以下两个映射:-
贴图属性
-
UnpackNormal
是unity内置的函数(在后面⑥定义片元着色器中会出现)。当我们把贴图纹理设置为Normal map类型时,该函数可以得到正确的法线方向。
不仅如此,Unity还可以根据不同平台来调整Normal的细节,使用UnpackNormal函数针对不同压缩格式对法线纹理进行正确采样。(目前存在DXT1、DXT5、DXT5nm的格式,DXT3以及被弃用——Tech-Artists)
可以在UnityCG.cginc之中找到UnpackNormal的具体定义:
inline fixed3 UnpackNormal(fixed4 packednormal) { #if defined(UNITY_NO_DXT5nm) return packednormal.xyz * 2 - 1; #else return UnpackNormalmapRGorAG(packednormal); #endif }
代码中可以看到我们可爱的Unity并不能识别除了DXT5nm以外的贴图格式,因此翻阅官方文档然后直接Ctrl-F搜索DXT得到如下结果:
说明在unity之中可以有2个选择,包括XYZ和DXT5nm,且更改法线编码时,最好直接在Unity的项目设置中,而不是在CG Shader代码中,否则解码成本会增加。- 根据下图可以看到,如果我们把类型设置为Normal map,将会出现
Create from Grayscale
的选项。这个选项的主要作用是可以将我们的先前讲到的 高度图 转换为 法线贴图,方便我们进行法线处理。
不仅如此,Unity给予了我们两种滤波器:
①Sharp——使用Sobel滤波
来生成法线
②Smooth
3、法线纹理的坐标空间
对于先前的纹理映射和纹理贴图之中,我们可以直到,要在CG语言中实现相应效果,不可或缺的是纹理的坐标空间(是ObjectSpace还是WorldSpace?)。而对于法线纹理的坐标空间来说,有个直接点的想法是——将修改后的模型空间中的表面法线存储在一张纹理中。(这样一来就可以结合前面“纹理贴图”一样实现凹凸效果。代码部分会加以证明。)这种纹理被称为——模型空间的法线纹理(Object-Space Normal Map)。
然而,实际上我们真正使用的是——模型顶点的切线空间(Tangent Space)。具体解释如下:
对于模型的每一个顶点,都有属于自己的切线空间,这个切线空间的原点就是顶点本身。
Z轴
是顶点的法线
方向 n
X轴
是顶点的切线
方向 t
Y轴
是由顶点的切线和法线叉积
而得代码开始
在切线空间下计算
① 在Properties属性下添加Bump法线纹理的属性,以及用于控制凹凸程度的属性
Properties{ _Color("Base Color",Color) = (1,1,1,1) _MainTex("Main Tex",2D) = "white" {} _BumpMap("Normal Map", 2D) = "bump"{} _BumpScale ("Bump Scale",Float) = 1.0 _Specular("Specular",Color) = (1,1,1,1) _Gloss("Gloss",Range(8.0,256)) = 20 }
_BumpMap,默认值可以使用“Bump”——Unity内置法线纹理。
_BumpScale为0时,意味着该法线纹理不会起作用。② 定义Tags
SubShader{ Pass{ Tags{"LightMode"="ForwardBase"}
- 声明顶点着色器和片元着色器 和 光照包含
#pragma vertex vert #pragma pragment prag #include "Lighting.cginc"
③ 为Properties中的属性定义类型
fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _BumpMap; float4 _BumpMap_ST; float _BumpScale; fixed4 _Specular; float _Gloss;
根据上面的代码,可以看到,_BumpMap和普通纹理类似,同样也声明了一个_ST变量,这也正说明我们前面的理解是正确的——这样一来就可以结合前面“纹理贴图”一样实现凹凸效果。
④ 定义我们的a2v和v2f结构体
struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; float3 lightDir : TEXCOORD1; float3 viewDir : TEXCOORD2; };
这里的TANGENT就是我们所说的切线。Unity会像传送POSITION一样,把TANGENT所包含的
每个顶点的切线方向填充到tangent之中
,但是需要注意的是,和法线方向的normal不同,tangent的类型是float4,而非float3.是因为其额外多了一个tangent.w
分量来决定切线空间的第三个坐标轴——副切线的方向性。⑤ 定义顶点着色器
v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw; fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal); o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex)); o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex)); return o; }
⑥ 定义片元着色器
fixed4 frag(v2f i) : SV_Target { fixed3 tangentLightDir = normalize(i.lightDir); fixed3 tangentViewDir = normalize(i.viewDir); fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); fixed3 tangentNormal; tangentNormal = UnpackNormal(packedNormal); tangentNormal.xy *= _BumpScale; tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir)); fixed3 halfDir = normalize(tangentLightDir + tangentViewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss); return fixed4(ambient + diffuse + specular, 1.0); }
⑦ 整体代码:
Shader "LeonShader/Shader_7_2_HeightMap"{ Properties{ _Color("Base Color",Color) = (1,1,1,1) _MainTex("Main Tex",2D) = "white" {} _BumpMap("Normal Map", 2D) = "bump"{} _BumpScale("Bump Scale",Float) = 1.0 _Specular("Specular",Color) = (1,1,1,1) _Gloss("Gloss",Range(8.0,256)) = 20 } SubShader{ Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _BumpMap; float4 _BumpMap_ST; float _BumpScale; fixed4 _Specular; float _Gloss; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; float3 lightDir: TEXCOORD1; float3 viewDir : TEXCOORD2; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw; fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal); o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex)); o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex)); return o; } fixed4 frag(v2f i) : SV_Target { fixed3 tangentLightDir = normalize(i.lightDir); fixed3 tangentViewDir = normalize(i.viewDir); fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); fixed3 tangentNormal; tangentNormal = UnpackNormal(packedNormal); tangentNormal.xy *= _BumpScale; tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir)); fixed3 halfDir = normalize(tangentLightDir + tangentViewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss); return fixed4(ambient + diffuse + specular, 1.0); } ENDCG } } FallBack "Specular" }
⑧效果图
在世界空间下计算
-
-
Unity Shader - Heightmap 高度贴图
2019-06-11 08:58:40目录:Unity Shader - 知识点目录(先占位,后续持续更新) 原文:Heightmap 版本:2019.1 Heightmap 高度贴图(也称为视差映射)是一个类似于法线映射的概念,但是这种技术更复杂,因此性能也更昂贵。高度贴图通常...目录:Unity Shader - 知识点目录(先占位,后续持续更新)
原文:Heightmap
版本:2019.1Heightmap
高度贴图(也称为视差映射)是一个类似于法线映射的概念,但是这种技术更复杂,因此性能也更昂贵。高度贴图通常与法线贴图一起使用,通常它们用于想给表面定义一个很大的凹凸效果使用。当法线贴图纹理修改表面的光照时,视差高度贴图又进一步修改了,以达到一种表面的遮挡效果。离相机近的表面的凹凸很明显很多,而远离相机的话凹凸效果变小,有的似乎被遮挡在相机视野外。
这种效果,它可以产生一个非常令人信服的3D几何效果,表面的凹凸效果有些会相互遮挡住,看起来真的像是3D几何体,但真实的几何体没有任何修改,因为这仅仅是绘制一个表面的效果。
高度贴图正常应该是张灰度图,白色代表凸起的部分,黑色代表凹下的部分。下面就是Albedo贴图和高度贴图的匹配使用。
一张Albedo色彩贴图,和一张高度贴图匹配。
从左向右说明:- 岩石墙材质只设置了Albedo贴图,没有设置法线贴图和高度贴图。
- 设置了法线贴图。修改了表面的光照,但岩石间没有相互遮挡效果。
- 这个精致的效果是使用了法线贴图和高度贴图。岩石看起来就像是从表面凸起来似的,靠近相机的岩石看起来可以遮挡着后面的岩石。
通常灰度图在高度图中使用很适合,但在遮挡贴图使用也非常适合。了解Occlusion Maps(遮挡贴图)的信息,我们下节将讲到。
-
THREE shader 多贴图过渡效果
2020-07-20 16:49:13使用shader开发多材质过渡效果 在片元着色器中,使用插值计算,计算需要过渡的两张图的颜色插值。 qq群:1082834010 -
Unity Shader - ddx/ddy偏导函数测试,实现:锐化、高度图、Flat shading应用-附件资源
2021-03-05 15:18:27Unity Shader - ddx/ddy偏导函数测试,实现:锐化、高度图、Flat shading应用-附件资源 -
Heightmap 高度图 Standard Shader系列11
2019-01-06 23:47:45Heightmap 高度图 本文档主要是对Unity官方手册的个人理解与总结(其实以翻译记录为主:&gt;) 仅作为个人学习使用,不得作为商业用途,欢迎转载,并请注明出处。 文章中涉及到的操作都是基于Unity2018.1... -
自制-unity根据任意噪点图生产卡通地形shader
2021-07-09 09:23:23利用noise图片完成一个卡通类的shader 1:可根据任意噪点图片动态生产地形 2:参数动态修改生成网格精度 3:根据高度不同,调整颜色 -
[Unity]基于高度的混合纹理Shader
2020-08-02 18:50:42我们希望沙子会更多的在缝隙里面出现,而石头越高的地方沙子应该要越少,那么就需要知道每一张贴图的深度信息,这里可以把贴图对应的高度图保存在每张贴图的alpha通道。通过对比每张贴图的高度差,就可以知道应该... -
Unity Shader - ddx/ddy偏导函数测试,实现:锐化、高度图、Flat shading应用、高度生成法线
2019-07-02 15:18:50文章目录 ddx, ddy 说明 DirectX - ddx, ddy OpenGL - dFdx, dFdy 伪代码表示 可用它来做什么 简单的边缘突出应用 Shader 边缘突出-锐化-增加差值 边缘突出-增加亮度 高度生成法线应用 准备一张高度图 Shader 整体... -
Three+Shader 生成3D地球高度地图
2020-03-17 00:20:15通过three.js + shader 生成高度地图 使用自定义着色器读取到当前位置信息的颜色值来生成当前高度。 准备 两张图,一张展示材质,一张生成高度的材质 高度是黑白构成的一张图,通过图片的灰度来生成高度。 开始 ... -
Unity3D 视差贴图(Parallax Mapping)Shader实现
2021-09-14 22:44:10而视差贴图实际上就是保存了顶点高度的一张贴图,我们利用这个高度来进行偏移,越高的地方偏移的越少,越矮的地方偏移的越多,这个从图上也可以看出来。 关键代码如下,在输入结构体中声明viewDir即切线空间中的... -
商业Shader渲染-深度图
2021-10-16 11:16:00万物皆可深度图 这个图如何得到的呢?和一般网上说的Unity方法,cg方法都有点不同 //VP矩阵(projectionMatrix),是将相机空间中的点从视锥体(frustum)变换到规则观察体(Canonical View Volume)中 //实际... -
Unity渲染(四):Shader着色器基础入门之获取当前屏幕贴图
2022-03-14 10:52:01之后将这个[-1,1]的坐标转换到屏幕空间,x坐标由[-1,1]->[0,屏幕宽度] ,y坐标由[-1,1]->[0,屏幕高度],这两步可以归结为以下公式 裁剪空间转为屏幕空间公式 但我们需要的是UV坐标,只需要转换到[0,1]即可,相当于... -
Unity Shader实现翻书效果
2020-08-25 08:19:06主要为大家详细介绍了Unity Shader实现翻书效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
Amplify Shader Editor 1.8.1 着色器编辑器附教程.zip
2020-06-01 13:47:39最新版Amplify Shader Editor 1.8.1 着色器编辑器附教程,限学习交流用,请支持正版 -
Android中Canvas绘图之Shader使用图文详解
2016-01-15 00:05:02概述我们在用Android中的Canvas绘制各种面图形时,可以通过Paint.setShader(shader)方法为画笔Paint设置shader,这样就可以绘制出多彩的图形。那么Shader是什么呢?做过GPU绘图的同学应该都知道这个词汇,Shader就是... -
简易Shader实践记录1-图片按高度局部隐藏
2019-11-21 10:29:211.图片按高度局部隐藏 先上最终效果图 图片透明处理方式1(Discarding Transparent Fragments) 关键字:discard(cutoff) 缺点:片段着色器读取RGBA贴图并与用户指定的范围值大小比较alpha值。如果 alpha值比范围... -
Unity URP管线的PBR材质及Tessallation Shader(Height Map高度贴图)
2020-10-04 14:46:17在使用URP管线的过程中发现默认的URP管线的shader是没有提供height map参数设置的,经过查找才知道URP管线中height map相关的功能需要自己写shader开启Tessallation(曲面细分)和Displacement(移位贴图)功能才能... -
【备份】《Unity Shader入门精要》配图
2021-04-13 12:18:40说明:本页面是书籍《Unity Shader入门精要》的随书彩图集锦,包含了书中所有的插图,使用时可通过图片编号进行搜索。 作者:冯乐乐 邮箱:lelefeng1992@gmail.com 前言 第2章 渲染流水线 图2.1 真实... -
unity shader 实现随意平面裁剪
2022-01-19 17:30:48unity shader 实现随意平面裁剪 shader文件:参考 Shader "Tut/Shader/ClipObj" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 pass {... -
Unity Shader实现动态雾效果
2020-08-19 06:32:09主要为大家详细介绍了Unity Shader实现动态雾效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
ShaderGraph节点详解完整版20210331
2021-08-31 09:28:35ShaderGraph节点详解完整版20210331 -
Unity&Shader案例篇—地图上热图分布
2018-07-09 18:29:06一、介绍 在2维平面地图上随机或者指定位置生成一系列的热量、能源或者其他需要表示的信息的分布图。可以通过切换不同的贴图表示不同的信息,如图所示表示的是该地区的降水量的变化如图所示为表示该地区的温度变化... -
Unity Shader 学习 003-Shader结构体 颜色叠加
2022-05-27 15:35:191.实现效果 2.代码分析 3.引用参数块中的参数 4.Shader结构体 4.1 a2v结构体 4.1 v2f结构体 5. 颜色叠加效果的实现 5.1 顶点着色器部分 5.2 片元着色器部分 -
【Shader】色调映射、视差贴图与实时阴影
2022-01-15 22:33:33//采样一张高度图 float height = tex2D(_ParallaxMap, i.uv); //构建TBN矩阵 float3 nDir = normalize(i.normal_Dir); float3 tDir = normalize(i.tangent_Dir); float3 bDir = normalize(i.binormal_Dir); float3... -
简单的水波shader
2018-08-28 14:45:17自己写的一个简单的水波shader,拉到material配一个水波贴图再拖给一个plane就可以看到一个简单的水波效果(主要是plane,quad就4个顶点,抖不起来啊),可以用来学习或者用来修改更高级的波纹,主要是给初学者入门... -
Unity Shader - 使用Noise噪点图生成简单山脉(使用tex2Dlod控制顶点高度)
2020-03-04 23:23:52之前有一篇:Unity Shader - Noise 噪点图 - 实现简单山脉 偶然在国外的一些Unity学习网站上看到在VS阶段使用tex2Dlod的使用。(我才知道该函数可以在VS使用啊,之前看过很多文档啊,书籍啊,都没有说明这点,真是... -
Unity Shader-Ambient Occlusion环境光遮蔽(AO贴图,GPU AO贴图烘焙,SSAO,HBAO)
2018-10-30 00:44:45下面看一下实现,首先,我们要烘焙一张贴图,那么最重要的就是怎样把贴图直接展开到uv上并且显示出来,其实也比较简单,我们可以在vertex shader中得到uv,但是我们不进行正常的mvp变换,而是直接把uv坐标的位置作为...