Unity Shader-法线贴图(Normal)及其原理
简介
以前经常听说“模型不好看啊,怎么办啊?”答曰“加法线”,”做了个高模,准备烘一下法线贴图”,“有的美术特别屌,直接画法线贴图”.....法线贴图到底是个什么鬼,当年天真的我真的被这个图形学的奇淫杂技忽悠了,然而毕竟本人还算有点刨根问底的精神,决定研究一下法线贴图的原理以及Unity下的实现。本人才疏学浅,如有错误,欢迎指正。
法线贴图是目前游戏开发中最常见的贴图之一。我们知道,一般情况下,模型面数越高,可以表现的细节越多,效果也越好。但是,由于面数多了,顶点数多了,计算量也就上去了,效果永远是和性能成反比的。怎么样用尽可能简单模型来做出更好的效果就成了大家研究的方向之一。纹理映射是最早的一种,通过纹理直接贴在模型表面,提供了一些细节,但是普通的纹理贴图只是影响最终像素阶段输出的颜色值,不能让模型有一些凹凸之类的细节表现。而法线贴图就是为了解决上面的问题,给我们提供了通过低面数模型来模拟高面数模型的效果,增加细节层次感,效果与高模相差不多,但是大大降低了模型的面数。
法线贴图原理
如果还是没理解,再看一套图片,同样一张图片,旋转180度后的结果完全相反。不信可以去截图放到MSPaint里面转一下试试,反正我是试了....
既然一个面的光照条件(亮度)的改变,就可以让我们感觉这个面有凹凸感,那么上面说的,通过改变法线来改变面上某点的光照条件,进而忽悠观察者,让他们感觉这个面有凹凸感的方法就行得通了。
假如下面是我们的低面数模型,上面是我们的高面数模型,上面的模型在计算光照时,由于面数多,每个面的法线方向不同,所以各个面的光照计算结果都不同,就有凹凸的感觉了,而下面的低模,只有一个面,整个面的光照条件都是一致的,就没有凹凸的感觉了。我们如果把上面的高模的法线信息保存下来,类似纹理贴图那样,存在一张图里,再给低模使用,低模就可以有跟高模一样的法线,进而在计算光照时达到和高模类似的效果,这也就是常说的烘法线的原理。
凹凸贴图(Bump Map)
- //Bump Map
- //by:puppet_master
- //2016.12.13
- Shader "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
- {
- //定义Tags
- Tags{ "RenderType" = "Opaque" }
- CGPROGRAM
- //引入头文件
- #include "Lighting.cginc"
- //定义Properties中的变量
- fixed4 _Diffuse;
- sampler2D _MainTex;
- //使用了TRANSFROM_TEX宏就需要定义XXX_ST
- float4 _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;
- };
- //定义顶点shader
- v2f 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;
- }
- //定义片元shader
- fixed4 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 frag
- ENDCG
- }
- }
- //前面的Shader失效的话,使用默认的Diffuse
- FallBack "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;
- #else
- return 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.14
- Shader "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
- {
- //定义Tags
- Tags{ "RenderType" = "Opaque" }
- CGPROGRAM
- //引入头文件
- #include "Lighting.cginc"
- //定义Properties中的变量
- fixed4 _Diffuse;
- sampler2D _MainTex;
- //使用了TRANSFROM_TEX宏就需要定义XXX_ST
- float4 _MainTex_ST;
- sampler2D _BumpMap;
- float _BumpScale;
- //定义结构体:vertex shader阶段输出的内容
- struct v2f
- {
- float4 pos : SV_POSITION;
- //转化纹理坐标
- float2 uv : TEXCOORD0;
- //tangent空间的光线方向
- float3 lightDir : TEXCOORD1;
- };
- //定义顶点shader
- v2f 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;
- }
- //定义片元shader
- fixed4 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 frag
- ENDCG
- }
- }
- //前面的Shader失效的话,使用默认的Diffuse
- FallBack "Diffuse"
- }
结果:
总结
Unity Shader-法线贴图(Normal)及其原理的更多相关文章
- 【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的实现
[Unity Shader](三) ---------------- 光照模型原理及漫反射和高光反射的实现 [Unity Shader](四) ------ 纹理之法线纹理.单张纹理及遮罩纹理的实现 ...
- 翻译:非常详细易懂的法线贴图(Normal Mapping)
翻译:非常详细易懂的法线贴图(Normal Mapping) 本文翻译自: Shaders » Lesson 6: Normal Mapping 作者: Matt DesLauriers 译者: Fr ...
- 3DShader之法线贴图(normal mapping)
凹凸贴图(bump mapping)实现的技术有几种,normal mapping属于其中的一种,这里实现在物体的坐标系空间中实现的,国际惯例,上图先: 好了讲下原理 可以根据高度图生成法线量图,生成 ...
- 法线贴图——Normal Mapping
对于不曾学过.用过法线贴图的人来说,提到法线贴图,经常会提到的问题是什么是法线贴图?法线贴图用于解决什么问题?法线贴图的原理是什么?本文将就这三个问题阐述本人的一些见解,各位不喜勿喷!!! 谈到法线贴 ...
- 【转载】法线贴图Nomal mapping 原理
法线贴图多用在CG动画的渲染以及游戏画面的制作上,将具有高细节的模型通过映射烘焙出法线贴图,贴在低端模型的法线贴图通道上,使之拥有法线贴图的渲染效果,却可以大大降低渲染时需要的面数和计算内容,从而达到 ...
- 【Unity Shader】六、使用法线贴图(Normal Map)的Shader
学习资料: http://www.sikiedu.com/course/37/task/456/show# http://www.sikiedu.com/course/37/task/458/show ...
- 【Unity Shader】(四) ------ 纹理之法线纹理、单张纹理及遮罩纹理的实现
笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...
- Unity 着色器训练营(2) - MVP转换和法线贴图
https://mp.weixin.qq.com/s/Qf4qT15s9bWjbVGh7H32lw 我们刚刚公布了Unity 2018.1中,Unity将会内置可视化编程工具Shader Graph, ...
- 【Unity Shader】(十) ------ UV动画原理及简易实现
笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...
随机推荐
- opencv 车牌字符分割 ANN网络识别字符
最近在复习OPENCV的知识,学习caffe的深度神经网络,正好想起以前做过的车牌识别项目,可以拿出来研究下 以前的环境是VS2013和OpenCV2.4.9,感觉OpenCV2.4.9是个经典版本啊 ...
- Django Celery定时任务和时间设置
1.Celery加入定时任务 Celery除了可以异步执行任务之外,还可以定时执行任务.在实例代码的基础上写个测试方法: #coding:utf- from celery.task.schedules ...
- Hanoi Factorys
题面 思路 这道题看似难的一匹,实际上也难的一批还好,甚至n^2 DP都有50分呢. 原谅我一失手成千古恨. 50分思路 就是sort后根据条件DP if (LIS[i].b>LIS[j].a) ...
- 注解实现json序列化的时候自动进行数据脱敏
https://blog.csdn.net/liufei198613/article/details/79009015
- ps不显示命令本身的进程号
当我们查看某个服务的进程时候,它会把命令本身的进程显示出来.如下图: 进程号2383 就是我命令本身的进程号,和我实际想看的进程无关 特别是在我们写脚本,kill进程时候会报错: 解决办法可以优化脚本 ...
- Eclipse 之开发环境的常用配置
一.Java智能提示 (1). 打开Eclipse,选择打开" Window - Preferences". (2). 在目录树上选择"Java-Editor-Conte ...
- catalan数的新理解
catalan数的新理解h[5]==h[4][0]+h[3][1]+h[2][2]+h[1][3]+h[0][4];对于这种递推式就是catalan数
- IE8及以下浏览器伪数组slice方法兼容处理
前几天同事说数组的slice方法在IE8下有兼容问题,我查阅了MDN,文档里面有提到IE8及以下浏览器中,DOM对象组成的伪数组通过call调用slice方法没有遵循标准行为 我做了个demo在IE8 ...
- 使用Vmware安装linux且配置终端可以连接虚拟机总结
首先是下载一个linux镜像,我下载的是:ubuntu-16.04.2-desktop-amd64.iso 1.使用vmware安装linux,都使用默认的配置就行了,最多改一下主机名什么的,密码最好 ...
- 【BZOJ 1005】 1005: [HNOI2008]明明的烦恼 (prufer数列+高精度)
1005: [HNOI2008]明明的烦恼 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 4981 Solved: 1941 Description ...