Unity3d Shader篇(九)— 世界空间法线纹理映射

news2024/9/21 0:40:41

文章目录

  • 前言
  • 一、什么是世界空间法线纹理映射?
    • 1. 世界空间法线纹理映射工作原理
    • 2. 什么是世界空间?
    • 3. 切线空间法线纹理映射和世界空间法线纹理映射对比
      • 世界空间法线纹理映射:
        • 优点:
        • 缺点:
      • 切线空间法线纹理映射:
        • 优点:
        • 缺点:
    • 4. 法线映射例图
  • 二、使用步骤
    • 1. Shader 属性定义
    • 2. SubShader 设置
    • 3. 渲染 Pass
    • 4. 定义结构体和顶点着色器函数
    • 5. 片元着色器函数
  • 三、效果
  • 四、总结


前言

法线纹理映射(Normal Mapping)是一种在计算机图形学中常用的技术,它可以在不增加几何体细节的情况下,为物体表面增加凹凸的视觉效果。在这篇博客中,我们将介绍一种特殊的法线纹理映射技术——世界空间法线纹理映射(World Space Normal Mapping)的实现原理和效果展示。


一、什么是世界空间法线纹理映射?

1. 世界空间法线纹理映射工作原理

世界空间法线纹理映射是一种常用的法线纹理映射技术,它在计算光照效果时将法线贴图中的法线向量从切线空间(Tangent Space)变换到世界空间(World Space),以实现更加真实的光照效果。与切线空间法线纹理映射相比,世界空间法线纹理映射不受物体旋转、缩放和变形等变换的影响,因此具有更高的灵活性和通用性。

2. 什么是世界空间?

在计算机图形学中,"世界空间"指的是一个三维坐标系,用来描述整个场景中各个物体的位置、旋转和缩放关系。它是一个全局的坐标系,相对于整个场景固定不变。在渲染过程中,所有的物体都是相对于世界空间进行位置和旋转的。

3. 切线空间法线纹理映射和世界空间法线纹理映射对比

世界空间法线纹理映射:

优点:

相对简单: 实现起来相对简单直观,不需要进行切线空间到世界空间的转换。
适用范围广: 对于一些不需要考虑物体形变的情况下,如地面、天空等,世界空间法线纹理映射是一种有效的技术。
不受物体形变影响: 由于是在世界空间中进行计算,不受物体形变的影响,对于静态物体效果较好。

缺点:

不适用于形变物体: 对于需要进行形变的物体,如角色动画、流体等,世界空间法线纹理映射无法准确表现表面细节。
纹理拉伸问题: 在物体形变时,可能会出现纹理拉伸或者变形的问题,影响视觉效果。

切线空间法线纹理映射:

优点:

适用于形变物体: 由于是在切线空间中进行计算,可以准确地跟随物体形变,适用于动态变化的物体表面。
纹理映射稳定: 在物体形变时,切线空间法线纹理映射能够保持纹理映射的稳定性,不会出现拉伸或变形的问题。

缺点:

计算复杂: 需要进行切线空间到世界空间的转换,计算复杂度较高,对性能有一定的要求。
需要切线信息: 需要额外的切线信息来进行计算,增加了模型制作和导入的复杂度。
不适用于全局效果: 在一些需要考虑全局效果的情况下,如全局光照、全局反射等,切线空间法线纹理映射可能会出现不理想的效果。

综上所述,世界空间法线纹理映射适用于静态物体或者不需要考虑形变的情况,实现简单但不适用于动态形变物体。而切线空间法线纹理映射适用于动态形变物体,能够准确跟随形变,但需要额外的切线信息并且计算复杂度较高。选择合适的方法取决于具体的应用场景和需求。

4. 法线映射例图

在这里插入图片描述
切线空间下法线映射图看起来几乎全部都是蓝色的,这是因为,每个法线方向所在的坐标空间是不一样的,即是表面每点各自的切线空间。切线空间是一种相对于物体表面的局部坐标系,它的原点是物体表面上的某个顶点,它的三个坐标轴分别是切线方向(Tangent)、副切线方向(Binormal)和法线方向(Normal)。

切线空间下法线映射图的RGB通道分别对应了切线空间的XYZ轴,也就是说,红色通道表示切线方向的分量,绿色通道表示副切线方向的分量,蓝色通道表示法线方向的分量。由于法线一般都是指向表面外侧,也就是切线空间的Z轴正方向,因此蓝色通道的值一般都比较大,接近于1.0,而红色和绿色通道的值则根据法线的偏移而变化,一般在0.0到1.0之间。因此,切线空间下法线映射图的颜色会偏向于蓝色,而且越是平坦的表面,越是纯蓝色,越是凹凸的表面,越是有其他颜色的混合。

二、使用步骤

1. Shader 属性定义

// 定义属性
Properties
{
   _MainTex("MainTex",2D)="white"{} // 主纹理贴图
   _BumpMap("Normal Map",2D)="bump"{} // 法线贴图
   _BumpScale("BumpScale",float)=1 // 法线贴图的缩放系数
   _Diffuse("Diffuse",Color)=(1,1,1,1) // 漫反射颜色属性,默认白色
   _Specular("Specular",Color)=(1,1,1,1) // 高光颜色属性,默认白色
   _Gloss("Gloss",Range(1,256))=5 // 高光反射系数
}

2. SubShader 设置

SubShader
{
    Tags
    {
        "RenderType" = "Opaque" // 渲染类型为不透明
    }
    
    LOD 100 // 细节级别
}

SubShader 定义了一组渲染设置,包括标签和细节级别。在这里,我们将渲染类型标签设置为 “Opaque”,表示物体是不透明的。

3. 渲染 Pass

Pass
{
    CGPROGRAM
    // 顶点着色器函数声明
    #pragma vertex vert
    // 片段着色器函数声明
     #pragma fragment frag

    // 包含Unity CG库
    #include "UnityCG.cginc"
    // 包含光照CG库
    #include "Lighting.cginc"

    // 漫反射颜色属性
    fixed4 _Diffuse;
    // 高光颜色属性
    fixed4 _Specular;
    // 高光系数属性
    float _Gloss;

    //贴图
    sampler2D _MainTex;
    //_MainTex附属属性,不需要在Properties定义
            float4 _MainTex_ST;

    //纹理法线贴图
    sampler2D _BumpMap;
    float4 _BumpMap_ST;
    float _BumpScale;
}

这里开始了渲染 Pass 部分。在这里,我们使用了 CGPROGRAM 指令来声明顶点着色器和片元着色器函数。#pragma vertex vert#pragma fragment frag 分别指定了顶点着色器函数和片元着色器函数的名称。

然后,我们包含了 UnityCG.cgincLighting.cginc,它们提供了许多有用的函数和宏,用于简化编写 Shader。

4. 定义结构体和顶点着色器函数

// 定义结构体:从顶点到片段的数据传递
struct v2f {
	float4 vertex: SV_POSITION; // 顶点位置
	float4 uv: TEXCOORD0; // 纹理坐标
	float4 TtoW0: TEXCOORD1; // 切线空间到世界空间的转换矩阵
	float4 TtoW1: TEXCOORD2; // 切线空间到世界空间的转换矩阵
	float4 TtoW2: TEXCOORD3; // 切线空间到世界空间的转换矩阵
};

// 顶点着色器函数
v2f vert(appdata_tan v) {
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex); // 顶点位置变换到裁剪空间

	// 让外面的属性可以影响到uv
	// o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
	// uv计算简化函数
	o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
	o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

	// 求世界顶点坐标
	float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
	// 求世界法线
	fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
	// 求世界切线
	fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
	// 求世界坐标的副切线
	fixed3 worldbinormal = cross(worldNormal, worldTangent) * v.tangent.w;

	// 按列摆放得到从切线空间到世界空间的变换矩阵
	o.TtoW0 = float4(worldTangent.x, worldbinormal.x, worldNormal.x, worldPos.x);
	o.TtoW1 = float4(worldTangent.y, worldbinormal.y, worldNormal.y, worldPos.y);
	o.TtoW2 = float4(worldTangent.z, worldbinormal.z, worldNormal.z, worldPos.z);

	return o;
}

顶点着色器的输入是一个结构体 appdata_tan ,它包含了顶点的位置、法线、切线和贴图坐标等信息。顶点着色器的输出是一个结构体 v2f ,它包含了顶点的裁剪空间位置、uv、切线空间到世界空间的转换矩阵等信息。

5. 片元着色器函数

// 片段着色器函数
fixed4 frag(v2f i): SV_Target {
	float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);

	// 计算世界空间下的光照方向和视角方向
	fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

	// 获得法线纹理

	// 获取切线空间下的光源方向
	// 法线贴图采样
	fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);

	// 把0,1还原成-1,1
	// 没有把textureType设置成normalMap属性
	// fixed3 tangentNormal;
	// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
	// tangentNormal.z = sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

	// 把textureType设置成normalMap属性
	// UnpackNormal解压packedNormal
	fixed3 tangentNormal = UnpackNormal(packedNormal);
	tangentNormal.xy *= _BumpScale;

	// 把切线空间法线转换到世界坐标
	fixed3 worldNormal = normalize(float3(dot(i.TtoW0.xyz, tangentNormal), dot(i.TtoW1.xyz, tangentNormal),
		dot(i.TtoW2.xyz, tangentNormal)));

	// 获取环境光
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

	// 纹理采样
	fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb;

	// 漫反射
	fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * max(0, dot(lightDir, worldNormal) * 0.5 + 0.5);

	// 高光反射
	// 计算半向量
	fixed3 halfDir = normalize(lightDir + viewDir);
	// 计算高光颜色   
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

	// 组合最终颜色
	fixed3 color = diffuse + ambient + specular;
	return fixed4(color, 1); // 输出颜色
}

片元着色器的输入是一个结构体 v2f ,包含了顶点的裁剪空间位置、uv、切线空间到世界空间的转换矩阵。片元着色器的输出是一个 fixed4 类型的颜色值,它表示了最终屏幕上的像素颜色。

三、效果

右:切线空间 左:世界空间
在这里插入图片描述

四、总结

法线纹理映射是一种技术,用于在低多边形模型上模拟高多边形模型的细节。它通过使用一张法线贴图,存储每个像素的法线方向,来影响光照计算。

法线纹理映射有两种常见的方式:世界空间法线纹理映射切线空间法线纹理映射。它们的区别在于法线贴图中的法线方向是相对于哪个坐标空间的。

世界空间法线纹理映射是指法线贴图中的法线方向是相对于世界空间的,它们是绝对的,不随模型的变换而变化。这种方式的优点是简单直观,不需要额外的计算或数据来转换法线方向。缺点是不适用于动态变换的模型,比如骨骼动画,因为法线方向不会随着模型的变形而变化,导致光照错误。

切线空间法线纹理映射是指法线贴图中的法线方向是相对于切线空间的,它们是相对的,随模型的变换而变化。切线空间是位于模型表面上的一个局部坐标空间,它的三个轴分别是切线方向、副切线方向和法线方向。这种方式的优点是通用性好,适用于动态变换的模型,因为法线方向会随着模型的变形而变化,保持正确的光照。缺点是需要额外的计算或数据来转换法线方向,比如需要存储或计算每个顶点的切线和副切线,以及构建一个从切线空间到世界空间的变换矩阵。

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

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

相关文章

专145+总420+哈尔滨工业大学803信号与系统和数字逻辑电路考研经验哈工大电子信息与通信,真题,大纲,参考书。

自从高考失利没有考入哈工大,一直带着遗憾,今年初试专业课803信号与系统和数字逻辑电路145,总分420顺利圆满哈工大,了却了一块心病,回看这一年的复习起起落落,心中的那块初心,让我坚持到了上岸&…

springmvc+ssm+springboot房屋中介服务平台的设计与实现 i174z

本论文拟采用计算机技术设计并开发的房屋中介服务平台,主要是为用户提供服务。使得用户可以在系统上查看房屋出租、房屋出售、房屋求购、房屋求租,管理员对信息进行统一管理,与此同时可以筛选出符合的信息,给笔者提供更符合实际的…

外汇天眼:外汇交易不可不知的8大风险!

现在外汇交易中的风险主要有哪些? 外汇作为一种投资方式肯定有风险,我们要想的是尽量规避风险。 今天就给大家介绍一下现在外汇交易中的风险主要有哪些? 一、高杠杆风险 由于外汇保证金交易采用的杠杆比例,放大了损失的额度&…

【前端素材】推荐优质后台管理系统APP Zina平台模板(附源码)

一、需求分析 当我们从多个层次来详细分析后台管理系统时,可以将其功能和定义进一步细分,以便更好地理解其在不同方面的作用和实际运作。 1. 功能层次 a. 用户管理功能: 用户注册和登录:管理用户账户的注册和登录过程。权限管…

在Win11上部署Stable Diffusion WebUI Forge

Stable Diffusion WebUI Forge 是 Stable Diffusion WebUI(基于 Gradio)之上的平台,可简化开发、优化资源管理并加快推理速度。“Forge”这个名字的灵感来自“Minecraft Forge”。这个项目旨在成为SD WebUI的Forge。 与原始 WebUI&#xff0…

【数据结构】链式队列

链式队列实现&#xff1a; 1.创建一个空队列 2.尾插法入队 3.头删法出队 4.遍历队列 一、main函数 #include <stdio.h> #include "./3.linkqueue.h" int main(int…

漫漫数学之旅030

文章目录 经典格言数学习题古今评注名人小传 - 柏拉图 经典格言 不知道正方形对角线与它的边不可通约的人&#xff0c;根本不配人这个称号。——柏拉图&#xff08;Plato&#xff09; 好家伙&#xff0c;想象一下柏拉图老兄正站在古希腊的广场上&#xff0c;手里挥舞着一根画着…

流程图:理解、创建与优化的视觉工具

流程图&#xff1a;理解、创建与优化的视觉工具 引言 在日常生活和工作中&#xff0c;我们经常遇到需要描述一系列步骤或过程的情况。这些步骤可能是制作一杯咖啡、完成一个项目&#xff0c;或者是解决一个复杂的数学问题。流程图&#xff0c;作为一种强大的视觉工具&#xf…

自然语言处理(NLP)—— 神经网络自然语言处理(2)实际应用

本篇文章的第一部分是关于探索词嵌入&#xff08;word embedding&#xff09;向量空间。词嵌入是一种语言模型和文本表示技术&#xff0c;其中单词或短语从词汇表被映射到向量的高维空间中。通过这种方式&#xff0c;可以通过计算向量之间的距离来捕捉单词之间的语义关系。 1.…

PC8260小封装COT控制模式同步降压器18V/6A输出电流只需极少外围元器

描述 PC8260是一个高效率的600kHz&#xff0c;恒定导通时间&#xff08;COT&#xff09;控制模式同步降压DC-DC转换器提供高达6A的电流。PC8260集成主开关和极低同步开关RDS&#xff08;ON&#xff09;以将传导损耗降至最低。低输出电压纹波和小型外部电感器电容器尺寸通过600…

【EndNote20】Endnote20和word的一些操作

文章目录 前言一、如何导入参考文献到EndNote201.1.在谷歌学术或知网上下载文献1.2.将下载好的文件导入EndNote20(可批量导入)1.3.书籍如何导入 二、Word中加入参考文献 前言 做毕设时学习了EndNote20的一些使用方法&#xff0c;并在此慢慢做汇总。 一、如何导入参考文献到End…

快速构建 Debezium MySQL Example 数据库

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

力扣226 翻转二叉树 Java版本

文章目录 题目描述解题思路代码 题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a;root…

JavaSE多线程线程池

文章目录 1. 多线程入门1.1 多线程相关概念1.2 什么是多线程1.3 多线程的创建方式1.3.1 继承 Thread 的方式1.3.2 实现 Runnable 接口的方式1.3.3 实现 Callable 接口的方式1.3.4 Thread 类中常用方法1.3.5 sleep() 方法 和 wait() 方法区别&#xff1a; 2. 线程安全2.1 线程安…

苍穹外卖Day02——总结2

前期文章 文章标题地址苍穹外卖Day01——总结1https://blog.csdn.net/qq_43751200/article/details/135466359?spm1001.2014.3001.5501苍穹外卖Day01——解决总结1中存在的问题https://lushimeng.blog.csdn.net/article/details/135473412 总结2 前期文章1. 新增员工模块1.1 …

Tomcat 学习之 Filter 过滤器

目录 1 Filter 介绍 2 Filter 的生命周期 3 Filter 和 FilterChain 4 Filter 拦截过程 5 FilterConfig 6 Filter 使用 1 Filter 介绍 在 Tomcat 中&#xff0c;Filter 是一种用于拦截请求和过滤响应的组件&#xff0c;可以在请求到达 Servlet 之前或响应离开 Servlet 之后…

宝塔面板mysql使用root账户远程登录

今日在弄数据库备份&#xff0c;我们两台服务器&#xff0c;一台测试环境一个正式环境&#xff1b;使用linux宝塔面板&#xff0c;数据库都是服务器本地mysql&#xff0c;打算在测试服务器添加远程数据库备份正式环境的数据库&#xff0c;需要注意的是添加远程服务器后必须点一…

QEMU源码全解析 —— virtio(22)

接前一篇文章&#xff1a;QEMU源码全解析 —— virtio&#xff08;21&#xff09; 前几回讲解了virtio驱动的加载。本回开始讲解virtio驱动的初始化。 在讲解virtio驱动的初始化之前&#xff0c;先要介绍virtio配置的函数集合变量virtio_pci_config_ops。实际上前文书也有提到…

开源博客项目Blog .NET Core源码学习(9:Autofac使用浅析)

开源博客项目Blog使用Autofac注册并管理组件和服务&#xff0c;Autofac是面向.net 的开源IOC容器&#xff0c;支持通过接口、实例、程序集等方式注册组件和服务&#xff0c;同时支持属性注入、方法注入等注入方式。本文学习并记录Blog项目中Autofac的使用方式。   整个Blog解…

hash,以及数据结构——map容器

1.hash是什么&#xff1f; 定义&#xff1a;hash,一般翻译做散列、杂凑&#xff0c;或音译为哈希&#xff0c;是把任意长度的输入&#xff08;又叫做预映射pre-image&#xff09;通过散列算法变换成固定长度的输出&#xff0c; 该输出就是散列值。这种转换是一种压缩映射&…