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](三) ----- ...
随机推荐
- 树链刨分(class版)
class版树链剖(刨)分 感谢沙华大佬的赞助 其实没什么太大变化,就是用了几次一顿乱指... CODE: #include<iostream> #include<cstdio> ...
- Android Service总结06 之AIDL
Android Service总结06 之AIDL 版本 版本说明 发布时间 发布人 V1.0 初始版本 2013-04-03 Skywang 1 AIDL介绍 AIDL,即And ...
- java 内部类 工厂方法
用内部类实现工厂模式 :优先使用类而不是接口,如果你的设计中需要某个接口,你必须了解它,否则不到迫不得已,不要将其放到你的类中 //: innerclasses/Factories.java impo ...
- hdu 1690 构图后Floyd 数据很大
WA了好多次... 这题要用long long 而且INF要设大一点 Sample Input2 //T1 2 3 4 1 3 5 7 //L1-L4 C1-C4 距离和花费4 2 //结点数 询问次 ...
- jQuery事件绑定—on()、bind()与delegate()
啃了一段日子的js相关了,学的过程中发现在jQuery中绑定事件时,有人用bind(),有人用on(),有人用delegate(),还有人用live(),看代码的时候觉得都实现功能了也就掀过去了,只是 ...
- MVC 区域路
VS2013和VS2015中MVC 区域路由匹配顺序相反 创建测试工程 分别在vs2013和vs2015中创建mvc项目,并创建First.Second.Three三个Area,每个Area下面创 ...
- 【AtCoder】ARC103
C - //// 为了防止一些多余的判断,我选择直接记录每个数的个数,然后枚举第一个数,找第一个数之外第二个数改变最少的情况下应该选什么 代码 #include <bits/stdc++.h&g ...
- Ubuntu 16.04设置rc.local开机启动命令/脚本的方法
Ubuntu 16.04设置rc.local开机启动命令/脚本的方法 Ubuntu 16.04设置rc.local开机启动命令/脚本的方法(通过update-rc.d管理Ubuntu开机启 ...
- 七夕情人节表白-纯JS实现3D心形+图片旋转
七夕情人节就快到了,这里献上纯js表白神器-心里都是你,预览: 技术点:css-3d.js-随机色.js-transform 1.html: <div class="heart&quo ...
- 1722 最优乘车 1997年NOI全国竞赛
题目描述 Description H城是一个旅游胜地,每年都有成千上万的人前来观光.为方便游客,巴士公司在各个旅游景点及宾馆,饭店等地都设置了巴士站并开通了一些单程巴上线路.每条单程巴士线路从某个巴士 ...