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](三) ----- ...
随机推荐
- activiti流程跟踪图算法
流程跟踪图-推导算法 工作中使用activiti实现流程图相关业务,但是上线后遇到问题,偶尔流程图出不来.查阅了一下画流程图的实现,基本上是参见:activiti-流程图颜色变化之一篇. 核心类,参见 ...
- NOIP 2000 计算器的改良
题面 NCL是一家专门从事计算器改良与升级的实验室,最近该实验室收到了某公司所委托的一个任务:需要在该公司某型号的计算器上加上解一元一次方程的功能.实验室将这个任务交给了一个刚进入的新手ZL先生. 为 ...
- ps不显示命令本身的进程号
当我们查看某个服务的进程时候,它会把命令本身的进程显示出来.如下图: 进程号2383 就是我命令本身的进程号,和我实际想看的进程无关 特别是在我们写脚本,kill进程时候会报错: 解决办法可以优化脚本 ...
- JS实现音乐播放器
JS实现音乐播放器 前 言 最近在复习JS,觉得音乐播放器是个挺有意思的东西,今天就来用我们最原生的JS写一个小小的音乐播放器~ 主要功能: 1.支持循环.随机播放 2.在播 ...
- All Friends 极大团
搞懂了什么是团 什么是极大团 什么是最大团 极大团就是 不是任何团的真子集 最大团就是点数最多的极大团 这题就是求极大团的个数 用bk算法 #include <iostream> ...
- 005.FTP本地用户访问
一 新建本地用户 [root@imxhy ftp]# useradd ftpuser #用于登陆ftp的用户 [root@imxhy ftp]# passwd ftpuser Changing pas ...
- 大数据技术之_16_Scala学习_02_变量
第二章 变量2.1 变量是程序的基本组成单位2.2 Scala 变量的介绍2.2.1 概念2.2.2 Scala 变量使用的基本步骤2.3 Scala 变量的基本使用2.4 Scala 变量使用说明2 ...
- JAVA 图形开发中组件对齐方法及界面开发
/*文章中用到的代码只是一部分,需要源码的可通过邮箱联系我 1978702969@qq.com*/ 在上篇博客中提到了JAVA图形界面开发时的两种布局,流式布局和边框布局. 在实际使用中可能会发现,往 ...
- Eclipse从SVN检出maven项目后的一些配置
Eclipse从SVN检出maven项目后,会发现它只是一个普通的java project,如图: 这里我们需要把它转成maven-webapp,并调整相关属性,设置运行环境,关联相关jar目录等. ...
- Nmap扫描教程之基础扫描详解
Nmap扫描教程之基础扫描详解 Nmap扫描基础扫描 当用户对Nmap工具了解后,即可使用该工具实施扫描.通过上一章的介绍,用户可知Nmap工具可以分别对主机.端口.版本.操作系统等实施扫描.但是,在 ...