shader 获取法线_Unity Shader 入门到改行5——法线贴图

the best of blur

1. 法线贴图理论

1.1 什么是法线贴图

一般的贴图中存储的是表面颜色值(RGBA),而法线贴图存放的则是法线信息(xyzw),假设某顶点处的 uv 坐标为 (u,v), 那么在法线贴图 (u,v)处纹素的值表示该顶点的“法线”方向。通常法线贴图中存储的并不是这个顶点的真实法线信息。

1.2 法线贴图的作用

想象一下,如果我们想要表现一个凹凸不平的模型表面(想象一个橙子的表面),有哪些办法呢?

直接把模型做成凹凸不平。这种方法最理想,效果也最好。但是模型需要太多顶点了,例如橙子表面的一个“坑”,需要增加额外的若干个顶点。

做一个一定精度的平滑模型(例如把橙子做成一个球体模型),把表面的”坑“或”凸点“信息,也就是某一点的”海拔“记录下来,渲染的时候根据这些信息动态生成顶点信息,得到凹凸不平的模型。不用说,这种方法需要单独的存储空间来记录凹凸信息,而且顶点动态生成将会非常消耗。

和第二种方法一样,做一个平滑模型,同样记录表面的“海拔”,渲染时不是动态生成顶点,而是根据“海拔”信息反推顶点的法线信息,通过光照效果来表现表面的”凹凸“。这种方法在计算光照时需要先进行表面法线的计算,比较消耗。

同样做一个光滑模型,不是记录表面的凹凸信息本身,而是记录”假定的凹凸情形下的法线信息“,渲染时根据“有偏差”的法线信息来进行光照计算,使得渲染出来的画面看起来凹凸不平。

上面第三种方法称为基于“高度纹理”的凹凸表现。而第四种方法就是基于“法线纹理”的凹凸表现。

注意:高度贴图和法线贴图用来表现“凹凸”,在模型轮廓的边缘会穿帮。比如你可以用这两种方法使一个平滑的橙子模型表面看起来凹凸不平,但是在橙子的边缘总是平滑的。

1.3 法线贴图纹素取值范围

通常贴图纹素用来表示 RGBA,那么每个分量的取值范围是[0,1],而法线的每个分量取值范围为[-1,1],所以用贴图纹素表示一个法线时,需要针对每一个分量做映射

pixel = (normal + 1) / 2;

在针对法线贴图采样后,进行逆运算

normal = 2 * pixel - 1;

得到实际的法线分量值。

1.4 法线贴图基于什么坐标系

法线贴图储存了表面法线,而法线是一个方向,那么这个方向是基于什么坐标系?通常跟随顶点数据一起传输到 顶点着色器中的法线,由 NORMAL 语义指定,是基于模型坐标系的。所以我们可以将法线在模型坐标中的值存储到法线贴图中,得到模型空间的法线贴图,而在实际制作中,应用更多的是顶点切线空间的法线贴图。

对于每个顶点,以顶点自身作为原点,顶点切线方向为x轴,法线方向为z轴,切线和法线方向叉乘得到 y 轴(副法线方向),得到这个顶点的 切线坐标空间,基于这个空间的法线记录下来得到 顶点切线空间的法线贴图。

左:模型空间的法线贴图 右:切线空间的法线贴图

模型空间法线贴图的优点

(1)实现简单,直观

(2)更平滑的缝合和边界处的表现。

切线空间法线贴图的优点

(1)可重用,记录的是“相对法线信息”,而模型空间的法线贴图记录的是“绝对法线信息”。

(2)可以做 UV 动画来实现凹凸移动效果。

(3)可压缩。z分量永远是正方向,可以只存储xy分量。

1.5 为什么切线空间的法线贴图看起来都是偏蓝色的?

切线空间的法线贴图保存的是基于顶点的切线空间中的法线数值,而在顶点的切线空间中,真实法线的反向永远是(0,0,1),经过上述的计算公式得到法线贴图中存储的值为 (0.5,0.5, 1),偏蓝色。而修改后的法线通常也是 z 值最大,因为你不太可能有90度以上的法线修改,整体还是偏蓝。

通常使用顶点切线空间的法线贴图,而顶点空间中的修改后的法线值,z分量最大,换算成颜色就是 b 分量最大,所以法线贴图通常看起来偏蓝色。

2. 如何在 Shader 中应用法线贴图

我们使用在切线空间下的法线贴图,先上完整 shader 代码,然后逐步分析,代码如下:

Shader "Shader_Examples/04_NormalTexture_TangentSpace"

{

Properties

{

_MainTex ("Texture", 2D) = "white" {}

_SpecularColor ("SpecularColor", Color) = (1,1,1,1)

_Gloss ("Gloss", Range(8, 256)) = 20

_BumpTex ("BumpTex", 2D) = "bump" {}

_BumpScale ("BumpScale", Float) = 1.0

}

SubShader

{

Tags { "RenderType"="Opaque" "LightMode" = "ForwardBase" }

Pass

{

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#include "UnityCG.cginc"

#include "Lighting.cginc"

sampler2D _MainTex;

float4 _MainTex_ST;

fixed4 _SpecularColor;

float _BumpScale;

sampler2D _BumpTex;

float4 _BumpTex_ST;

float _Gloss;

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

float4 tangent : TANGENT;

float3 normal : NORMAL;

};

struct v2f

{

float2 uv : TEXCOORD0;

float4 vertex : SV_POSITION;

float3 lightDir : TEXCOORD1;

float3 viewDir : TEXCOORD2;

};

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 模型空间副法线

fixed3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;

float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);

float3 lightDir = ObjSpaceLightDir(v.vertex);

float3 viewDir = ObjSpaceViewDir(v.vertex);

o.lightDir = mul(rotation, lightDir);

o.viewDir = mul(rotation, viewDir);

o.uv = v.uv;

return o;

}

fixed4 frag (v2f i) : SV_Target

{

float3 lightDir = normalize(i.lightDir);

float3 viewDir = normalize(i.viewDir);

float3 halfDir = normalize(lightDir + viewDir);

float4 packedNormal = tex2D(_BumpTex, i.uv);

float3 tangentNormal = UnpackNormal(packedNormal);

tangentNormal.xy *= _BumpScale;

tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

fixed3 albedo = tex2D(_MainTex, i.uv).rgb;

fixed3 diffuse = _LightColor0.rgb * albedo.rgb * saturate(dot(tangentNormal, lightDir));

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;

fixed3 specular = _SpecularColor * _LightColor0 * pow(saturate(dot(halfDir, tangentNormal)), _Gloss);

return fixed4(diffuse + ambient + specular, 1.0);

}

ENDCG

}

}

}

渲染效果如图:

法线贴图效果

2.1 shader 属性与对应的变量

Properties

{

_MainTex ("Texture", 2D) = "white" {}

_SpecularColor ("SpecularColor", Color) = (1,1,1,1)

_Gloss ("Gloss", Range(8, 256)) = 20

_BumpTex ("BumpTex", 2D) = "bump" {}

_BumpScale ("BumpScale", Float) = 1.0

}

漫反射纹理 _MainTex, 高光颜色 _SpecularColor 和高光系数 _Gloss 没什么好说的,新增的纹理 _BumpTex 为法线贴图,默认值为 unity 内置法线贴图 "bump",_BumpScale 用来控制表面的“凹凸”程度,后面会分析它是怎么起作用的。对应的变量声明:

sampler2D _MainTex;

float4 _MainTex_ST;

fixed4 _SpecularColor;

float _BumpScale;

sampler2D _BumpTex;

float4 _BumpTex_ST;

float _Gloss;

2.2 着色器输入结构

struct appdata

{

float4 vertex : POSITION;

float2 uv : TEXCOORD0;

float4 tangent : TANGENT;

float3 normal : NORMAL;

};

struct v2f

{

float2 uv : TEXCOORD0;

float4 vertex : SV_POSITION;

float3 lightDir : TEXCOORD1;

float3 viewDir : TEXCOORD2;

};

语义TANGENT指定的切线是一个 float4 类型的变量,而语义NORMAL指定的法线是 float3 类型,因为 TANGENT 的z分量需要用来确定 副法线 的方向,下一个段落会介绍如何计算副法线

因为使用了顶点切线空间下的法线贴图,我们需要把所有的光照计算都变换到顶点切线空间下,在顶点着色器中将光线方向lightDir和视线方向viewDir变换到顶点切线空间,再输入到片元着色器中。

因为我们这里没有涉及到纹理的 ST 变化,所以 _MainTex 和 _BumpTex 功用纹理坐标

v2f 中并没有定义法线,因为我们这里使用的是发现贴图中的法线,而不直接使用顶点法线了

2.3 顶点着色器

v2f vert (appdata v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

// 模型空间副法线

fixed3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;

// 模型空间到顶点切线空间的变换矩阵

float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);

// 光线方向和视线防线变换到顶点切线空间

float3 lightDir = ObjSpaceLightDir(v.vertex);

float3 viewDir = ObjSpaceViewDir(v.vertex);

o.lightDir = mul(rotation, lightDir);

o.viewDir = mul(rotation, viewDir);

o.uv = v.uv;

return o;

}

顶点的法线:顶点所在的所有平面的法线加权平均,得到顶点法线

顶点的切线:我们都知道顶点切线与顶点法线垂直、但与顶点法线垂直的方向有很多?哪一条是顶点切线呢?约定俗成 切线最终规定为顶点 uv 坐标中的 u 方向,可以参考文末的参考文章1。

顶点的副法线:由法线和切线叉乘得到,方向性由顶点切线的z分量确定。

如何计算模型空间到顶点切线空间的变换矩阵:参考我的推导过程模型空间到顶点切线空间变换矩阵的推导。结论就是:将模型空间下的切线、副法线、法线按行排列得到变换矩阵。

在顶点着色器中将光线方向和视线方向变换到顶点的切线空间并传递给片元着色器。

2.4 片元着色器

fixed4 frag (v2f i) : SV_Target

{

float3 lightDir = normalize(i.lightDir);

float3 viewDir = normalize(i.viewDir);

float3 halfDir = normalize(lightDir + viewDir);

float4 packedNormal = tex2D(_BumpTex, i.uv);

float3 tangentNormal = UnpackNormal(packedNormal);

tangentNormal.xy *= _BumpScale;

tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

fixed3 albedo = tex2D(_MainTex, i.uv).rgb;

fixed3 diffuse = _LightColor0.rgb * albedo.rgb * saturate(dot(tangentNormal, lightDir));

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;

fixed3 specular = _SpecularColor * _LightColor0 * pow(saturate(dot(halfDir, tangentNormal)), _Gloss);

return fixed4(diffuse + ambient + specular, 1.0);

}

如何从法线贴图中得到法线:tex2D采样 _BumpTex 得到该点的法线像素值,需要计算出对应的xyz值,因我们已经在 Unity 编辑器中将 _BumpTex 设置为 "Normal Map" ,所以内置方法 UnpackNormal 已经执行了这个计算

albedo,diffuse,ambient,specular 的计算不用多说了

_BumpScale 的作用:用来控制“凹凸程度”,当 _BumpScale 为0时,表示该点的顶点法线和法线贴图中采样出的法线重合,说明该点没有“凹凸”,_BumpScale 绝对值越大,表示该点的顶点法线和贴图中的法线偏差越远,说明“凹凸感”越明显。

下面5个胶囊体的 _BumpScale 取值分别为 2/1/0/-1/-2

不同的_BumpScale凹凸效果

3. Unity中的法线贴图类型设置

在上面的片元着色器中,我们从法线贴图中采样出纹素后,使用了 Unity 内置函数 UnpackNormal 来计算最终的法线值。只有正确的设置图片的类型为 "Normal Map" 时,使用这个内置函数才能得到正确结果,在 Unity 中的设置面板如下:

法线贴图设置

Create from Grayscale 表示是否“高度图”生成的纹理贴图。当我们在贴图中记录的是相对高度(黑色表示更低,白色表示更高)时,除了要设置类型为“Normal Map”之外,还要勾选这个选项,这个贴图就会被当成纹理贴图使用了。

勾选了 Create from Grayscale 之后,有两个选项:bumpness表示凹凸程度,filtering 决定了如何生成纹理贴图,smooth 表示生成的法线过渡比较平滑,而sharp 则表示法线过渡比较锋利。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/527049.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

主站系统服务器选择,配电网自动化主站系统的结构_功能及操作系统的选择.doc...

业界建设行业专版水电暖通配电网自动化主站系统的结构,功能及操作系统的选择"徐德勇摘 要:本文主要从系统结构"子系统划分"设计实施"操作系统的选择"及其功能等方面介绍了配电自动化主站系统%关键词:配电自动化*系统…

python切面异常处理_Spring项目中优雅的异常处理

Spring项目中优雅的异常处理前言如今的Java Web项目多是以 MVC 模式构建的,通常我们都是将 Service 层的异常统一的抛出,包括自定义异常和一些意外出现的异常,以便进行事务回滚,而 Service 的调用者 Controller 则承担着异常处理的…

ubuntu查看服务器信息,查看Ubuntu服务器的版本信息

Docker on YARN在Hulu的实现这篇文章是我来Hulu这一年做的主要工作,结合当下流行的两个开源方案Docker和YARN,提供了一套灵活的编程模型,目前支持DAG编程模型,将会支持长服务编程模型. 基于Voidbox,开发者可以很 ...使用angularJS遇见的一些问题的解决方案1. angularJS的$http.…

方法 手写promise_JS探索-手写Promise

无意间在知乎上刷到Monad这个概念,去了解了一下,前端的Promise就是一种Monad模式,所以试着学习一下手写一个Promise.本文内容主要参考于只会用?一起来手写一个合乎规范的Promise​www.jianshu.comPromise是什么所谓Promise&#x…

dcs服务器性能指标,第6章DCS的性能指标.PDF

第6章DCS的性能指标第 6 章 DCS 的性能指标随着 DCS 的广泛应用,对 DCS 的可靠性要求也越来越高,因此正确评价 DCS 的可靠性越来越显得重要。DCS 的可靠与否,通常包含两重意思:一是指产品在规定的时间内,完成规定功能的…

12如何隐藏dock栏_一键隐藏 iPhone 刘海和底部 Dock 栏,简洁又好看

技能: 隐藏刘海和底部dock 栏难度系数:2颗星适用系统:iOS 13(部分非iOS13也适用)最近,小雷打开手机,看到最多的关键词,就是:iOS13又双叒叕更新了。。。而且也看到不止一位网友晒这个有趣的新功能…

湖南hp服务器虚拟化解决方案,HP刀片服务器虚拟化整合解决方案-20210729062411.docx-原创力文档...

HP刀片办事器 虚拟化整合解决方案22 -目录TOC \o "1-3" \h \u 1.办事器硬件整合 - 2 -什么是办事器整合 - 2 -1.2 为何要做办事器整合 - 2 -2.刀片办事器的观点 - 3 -3.刀片办事器如何整合IT资源 - 4 -3.1 新一代数据中心对IT设施系…

宝塔php安装那个合_使用宝塔面板安装nextcloud | 启用本地存储 | 安装smbclient

宝塔面板安装nextcloud | 启用本地存储使用宝塔面板搭建nextcloud服务后,在设置外部存储时总是无法启用本地存储。问题1:提示:“smbclient” 未安装。无法挂载 "SMB / CIFS", "SMB / CIFS 使用 OC 登录信息"。请联系管理…

springbboot加密打包_Spring Boot 配置 Security 密码加密

依赖org.springframework.bootspring-boot-starter-security注入beanSpringBootApplicationpublic class UserApplication {public static void main(String[] args) {SpringApplication.run(UserApplication.class, args);}Beanpublic BCryptPasswordEncoder encoding(){retur…

mysql groupby 拼接_mysql groupby 字段合并问题(group_concat)

在我们的日常mysql查询中,我们可能会遇到这样的情况:对表中的所有记录进行分类,并且我需要得到每个分类中某个字段的全部成员。上面的话,大家看起来可能不太好懂,下面举一个例子来给大家说明。现在我们有一张表&#x…

mysql 基础教程 西泽 好吗_Mysql基础教程

往期推荐SQL语句分类DDL:数据定义语言,用来定义数据库对象:库、表、列等DML:数据操作语言,用来定义数据库记录(数据)DCL:数据控制语言,用来定义访问权限和安全级别;DQL:数…

mysql 字符集测试_关于字符集的测试报告_MySQL

nls_lang用于设置客户端的字符集。影响sqlplus界面的语言。regedit:hkey_local_machine/software/oracle/home0nls_lang键值1、取值为‘US7ASCII或为空2、取值为‘simplified chinese_china.ZHS16GBKnls_characterset用于设置数据库内部字符集,决定数据记录保存的格…

mysql数学函数名_MYSQL 常见数学函数说明

摘要:下文讲述MySQL常见数学函数的说明,如下所示:mysql > select round(2.34);-------------| round(2.34) |-------------| 2 |-------------1 row in set (0.00 sec)mysql > select round(2.34,1);---------------| round(2.34,1) |---------------| 2.3 |-…

mysql聚集索引 myisam_一句话说清聚集索引和非聚集索引以及MySQL的InnoDB和MyISAM

版权声明:本文为博主原创文章,未经博主允许不得转载。https://blog.csdn.net/21aspnet/article/details/89303988聚集索引和非聚集索引以及MySQL的InnoDB和MyISAM经常遇到有人向我咨询这个问题,其实呢,网上帖子很多,也…

mysql for loop_MySQL循环语句 - Linux就该这么学的个人空间 - OSCHINA - 中文开源技术交流社区...

mysql常见的三种循环方式:while、repeat和loop循环。还有一种goto,不推荐使用。1.while循环-- 设置mysql分隔符为//,也就意味着,当遇到下一个//时,整体执行SQL语句DELIMITER //DROP PROCEDURE if EXISTS ‘test’; # 如…

mysql的存储过程放在哪里_mysql存储过程求解,错误在哪里?

DELIMITER $$ALTER PROCEDURE sp_test(vdnId CHAR(2),startTimeStr CHAR(8))BEGINDECLARE v_startTime DATETIME;DECLARE v_endTime DATETIME;DECLARE v_VDNName CHAR(2);DECLARE v_sql VARCHAR(1000);/*判断输入参数的合法性*/SELECT t.VDN_name INTO v_VDNName FROM gdmdw.t_…

ue4插件导入导出_Blender到UE4的无缝衔接

Hello . 大家好本文给大家介绍一下Send To Unreal插件我是Vee1简介Send To Unreal是Epic官方开发的用于Blender和UE4快速同步的插件,支持静态物体、骨骼物体、动画等等。省去了Blender导出-选择目录文件-UE4导入这个中间步骤,效率提升不是一般得多。插件…

mysql数据库管理系统模式_MYSQL命令行模式管理MySql的一点心得

MYSQL命令行模式管理MySql的一点心得MYSQL命令行模式管理MySql的一点心得MySql数据库是中小型网站后台数据库的首选,因为它对非商业应用是免费的.网站开发者可以搭建一个"LinuxApachePHPMySql"平台,这是一个最省钱的高效平台.在使用MySql进行开发时,MySql自带的文档对…

mysql点击计数器_MySql计数器,如网站点击数,如何实现高性能高并发的计数器功能...

MySql计数器,如网站点击数,如何实现高性能高并发的计数器功能Clicks: 5338 Date: 2014-03-29 23:30:42 Power By 李轩LaneTagMysql计数器高性能现在有很多的项目,对计数器的实现甚是随意,比如在实现网站文章点击数的时候&#xff…

python 微服务架构_微服务架构(Python)

在后端开发方面,Java的使用呢要远比Python广泛,所以Java的微服务框架非常流行,但Python的微服务框架却很少有人问津。在大多数需要微服务的场合下直接用Java的各种工具就可以解决问题,但如果业务代码使用Python写的,那…