Unity Shader-法线贴图(Normal)及其原理
来源:互联网 发布:海康网络视频解码器 编辑:程序博客网 时间:2024/04/29 01:11
简介
以前经常听说“模型不好看啊,怎么办啊?”答曰“加法线”,”做了个高模,准备烘一下法线贴图”,“有的美术特别屌,直接画法线贴图”.....法线贴图到底是个什么鬼,当年天真的我真的被这个图形学的奇淫杂技忽悠了,然而毕竟本人还算有点刨根问底的精神,决定研究一下法线贴图的原理以及Unity下的实现。本人才疏学浅,如有错误,欢迎指正。
法线贴图是目前游戏开发中最常见的贴图之一。我们知道,一般情况下,模型面数越高,可以表现的细节越多,效果也越好。但是,由于面数多了,顶点数多了,计算量也就上去了,效果永远是和性能成反比的。怎么样用尽可能简单模型来做出更好的效果就成了大家研究的方向之一。纹理映射是最早的一种,通过纹理直接贴在模型表面,提供了一些细节,但是普通的纹理贴图只是影响最终像素阶段输出的颜色值,不能让模型有一些凹凸之类的细节表现。而法线贴图就是为了解决上面的问题,给我们提供了通过低面数模型来模拟高面数模型的效果,增加细节层次感,效果与高模相差不多,但是大大降低了模型的面数。
法线贴图原理
如果还是没理解,再看一套图片,同样一张图片,旋转180度后的结果完全相反。不信可以去截图放到MSPaint里面转一下试试,反正我是试了....
既然一个面的光照条件(亮度)的改变,就可以让我们感觉这个面有凹凸感,那么上面说的,通过改变法线来改变面上某点的光照条件,进而忽悠观察者,让他们感觉这个面有凹凸感的方法就行得通了。
假如下面是我们的低面数模型,上面是我们的高面数模型,上面的模型在计算光照时,由于面数多,每个面的法线方向不同,所以各个面的光照计算结果都不同,就有凹凸的感觉了,而下面的低模,只有一个面,整个面的光照条件都是一致的,就没有凹凸的感觉了。我们如果把上面的高模的法线信息保存下来,类似纹理贴图那样,存在一张图里,再给低模使用,低模就可以有跟高模一样的法线,进而在计算光照时达到和高模类似的效果,这也就是常说的烘法线的原理。
凹凸贴图(Bump Map)
//Bump Map//by:puppet_master//2016.12.13Shader "ApcShader/BumpMap"{//属性Properties{_Diffuse("Diffuse", Color) = (1,1,1,1)_MainTex("Base 2D", 2D) = "white"{}_BumpMap("Bump Map", 2D) = "black"{}_BumpScale ("Bump Scale", Range(0.1, 30.0)) = 10.0}//子着色器SubShader{Pass{//定义TagsTags{ "RenderType" = "Opaque" }CGPROGRAM//引入头文件#include "Lighting.cginc"//定义Properties中的变量fixed4 _Diffuse;sampler2D _MainTex;//使用了TRANSFROM_TEX宏就需要定义XXX_STfloat4 _MainTex_ST;sampler2D _BumpMap;float4 _BumpMap_TexelSize;float _BumpScale;//定义结构体:应用阶段到vertex shader阶段的数据struct a2v{float4 vertex : POSITION;float3 normal : NORMAL;float4 texcoord : TEXCOORD0;};//定义结构体:vertex shader阶段输出的内容struct v2f{float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;//转化纹理坐标float2 uv : TEXCOORD1;};//定义顶点shaderv2f vert(a2v v){v2f o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);//把法线转化到世界空间o.worldNormal = mul(v.normal, (float3x3)_World2Object);//通过TRANSFORM_TEX宏转化纹理坐标,主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;}//定义片元shaderfixed4 frag(v2f i) : SV_Target{//unity自身的diffuse也是带了环境光,这里我们也增加一下环境光fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.xyz;//归一化法线,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出的fixed3 worldNormal1 = normalize(i.worldNormal);//采样bump贴图,需要知道该点的斜率,xy方向分别求,所以对于一个点需要采样四次fixed bumpValueU = tex2D(_BumpMap, i.uv + fixed2(-1.0 * _BumpMap_TexelSize.x, 0)).r - tex2D(_BumpMap, i.uv + fixed2(1.0 * _BumpMap_TexelSize.x, 0)).r;fixed bumpValueV = tex2D(_BumpMap, i.uv + fixed2(0, -1.0 * _BumpMap_TexelSize.y)).r - tex2D(_BumpMap, i.uv + fixed2(0, 1.0 * _BumpMap_TexelSize.y)).r;//用上面的斜率来修改法线的偏移值fixed3 worldNormal = fixed3(worldNormal1.x * bumpValueU * _BumpScale, worldNormal1.y * bumpValueV * _BumpScale, worldNormal1.z);//把光照方向归一化fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//根据半兰伯特模型计算像素的光照信息fixed3 lambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;//最终输出颜色为lambert光强*材质diffuse颜色*光颜色fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz + ambient;//进行纹理采样fixed4 color = tex2D(_MainTex, i.uv);return fixed4(diffuse * color.rgb, 1.0);}//使用vert函数和frag函数#pragma vertex vert#pragma fragment fragENDCG}}//前面的Shader失效的话,使用默认的DiffuseFallBack "Diffuse"}效果如下:
法线贴图(Normal Map)
法线贴图是怎样存储的
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;}inline fixed3 UnpackNormal(fixed4 packednormal){#if defined(UNITY_NO_DXT5nm)return packednormal.xyz * 2 - 1;#elsereturn UnpackNormalDXT5nm(packednormal);#endif}做法很简单,乘2 减1大法好,转化区间没烦恼(什么鬼....)
为什么法线贴图存储在切线空间
N = T × normalize(dx/dv, dy/dv, dz/dv)
B = N × T
// Declares 3x3 matrix 'rotation', filled with tangent space basis#define TANGENT_SPACE_ROTATION \float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )
为什么法线贴图都是蓝色的
Unity下法线贴图Shader实现
//Bump Map//by:puppet_master//2016.12.14Shader "ApcShader/NormalMap"{//属性Properties{_Diffuse("Diffuse", Color) = (1,1,1,1)_MainTex("Base 2D", 2D) = "white"{}_BumpMap("Bump Map", 2D) = "bump"{}_BumpScale ("Bump Scale", Range(0.1, 30.0)) = 10.0}//子着色器SubShader{Pass{//定义TagsTags{ "RenderType" = "Opaque" }CGPROGRAM//引入头文件#include "Lighting.cginc"//定义Properties中的变量fixed4 _Diffuse;sampler2D _MainTex;//使用了TRANSFROM_TEX宏就需要定义XXX_STfloat4 _MainTex_ST;sampler2D _BumpMap;float _BumpScale;//定义结构体:vertex shader阶段输出的内容struct v2f{float4 pos : SV_POSITION;//转化纹理坐标float2 uv : TEXCOORD0;//tangent空间的光线方向float3 lightDir : TEXCOORD1;};//定义顶点shaderv2f vert(appdata_tan v){v2f o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);//这个宏为我们定义好了模型空间到切线空间的转换矩阵rotation,注意后面有个;TANGENT_SPACE_ROTATION;//ObjectSpaceLightDir可以把光线方向转化到模型空间,然后通过rotation再转化到切线空间o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));//通过TRANSFORM_TEX宏转化纹理坐标,主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;}//定义片元shaderfixed4 frag(v2f i) : SV_Target{//unity自身的diffuse也是带了环境光,这里我们也增加一下环境光fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse.xyz;//直接解出切线空间法线float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));//normalize一下切线空间的光照方向float3 tangentLight = normalize(i.lightDir);//根据半兰伯特模型计算像素的光照信息fixed3 lambert = 0.5 * dot(tangentNormal, tangentLight) + 0.5;//最终输出颜色为lambert光强*材质diffuse颜色*光颜色fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz + ambient;//进行纹理采样fixed4 color = tex2D(_MainTex, i.uv);return fixed4(diffuse * color.rgb, 1.0);}//使用vert函数和frag函数#pragma vertex vert#pragma fragment fragENDCG}}//前面的Shader失效的话,使用默认的DiffuseFallBack "Diffuse"}结果:
总结
- Unity Shader-法线贴图(Normal)及其原理
- Unity Shader-法线贴图(Normal)及其原理
- Unity Shader-法线贴图(Normal)及其原理
- Unity Shader-法线贴图(Normal)及其原理URL和固定管线着色器
- Unity Shader-法线贴图(Normal Map)与视差贴图(Parallax Map)
- Unity Shader-法线贴图(Normal Map)与视差贴图(Parallax Map)
- unity法线贴图原理
- Unity Shader 什么是法线贴图
- shader复习与深入:Normal Map(法线贴图)Ⅱ(转)
- shader复习与深入:Normal Map(法线贴图)Ⅰ
- shader复习与深入:Normal Map(法线贴图)Ⅱ
- shader复习与深入:Normal Map(法线贴图)Ⅰ(转)
- Normal Map(法线贴图)Ⅱ(转)
- Normal Mapping 法线贴图
- 法线贴图(Normal Mapping)
- Unity 法线贴图、高光贴图、Cube Map shader
- Unity Shader 在Shader中使用法线贴图
- Shader山下(四)法线贴图
- 下载文件组件
- 对于泥球型状态机,估计你也苦恼!
- PHP 判断中英文
- Swift Playground
- String字符串类型详解以及stringbuffer
- Unity Shader-法线贴图(Normal)及其原理
- PAT A1009
- Netty系列-使用Google Protobuf编解码
- JS之自定义属性的运用
- static
- HDU - 1406 完数(完全数)
- UML项目练习-仓库管理系统
- matlab神经网络
- 查找OPNET函数,修改OPNET的帮助文档,让它变得更直接更好用