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

2024-02-23 14:52

本文主要是介绍Unity3d Shader篇(九)— 世界空间法线纹理映射,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 一、什么是世界空间法线纹理映射?
    • 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解压packedNormalfixed3 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 类型的颜色值,它表示了最终屏幕上的像素颜色。

三、效果

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

四、总结

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

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

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

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

这篇关于Unity3d Shader篇(九)— 世界空间法线纹理映射的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/738993

相关文章

Open3D 基于法线的双边滤波

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 输入参数: 输出参数: 参数影响: 2.2完整代码 三、实现效果 3.1原始点云 3.2滤波后点云 Open3D点云算法汇总及实战案例汇总的目录地址: Open3D点云算法与点云深度学习案例汇总(长期更新)-CSDN博客 一、概述         基于法线的双边

揭秘世界上那些同时横跨两大洲的国家

我们在《世界人口过亿的一级行政区分布》盘点全球是那些人口过亿的一级行政区。 现在我们介绍五个横跨两州的国家,并整理七大洲和这些国家的KML矢量数据分析分享给大家,如果你需要这些数据,请在文末查看领取方式。 世界上横跨两大洲的国家 地球被分为七个大洲分别是亚洲、欧洲、北美洲、南美洲、非洲、大洋洲和南极洲。 七大洲示意图 其中,南极洲是无人居住的大陆,而其他六个大洲则孕育了众多国家和

Unity3D自带Mouse Look鼠标视角代码解析。

Unity3D自带Mouse Look鼠标视角代码解析。 代码块 代码块语法遵循标准markdown代码,例如: using UnityEngine;using System.Collections;/// MouseLook rotates the transform based on the mouse delta./// Minimum and Maximum values can

Unity3D 运动之Move函数和translate

CharacterController.Move 移动 function Move (motion : Vector3) : CollisionFlags Description描述 A more complex move function taking absolute movement deltas. 一个更加复杂的运动函数,每次都绝对运动。 Attempts to

【高等代数笔记】线性空间(一到四)

3. 线性空间 令 K n : = { ( a 1 , a 2 , . . . , a n ) ∣ a i ∈ K , i = 1 , 2 , . . . , n } \textbf{K}^{n}:=\{(a_{1},a_{2},...,a_{n})|a_{i}\in\textbf{K},i=1,2,...,n\} Kn:={(a1​,a2​,...,an​)∣ai​∈K,i=1,2,...,n

win7系统中C盘空间缩水的有效处理方法

一、深度剖析和完美解决   1、 休眠文件 hiberfil.sys :   该文件在C盘根目录为隐藏的系统文件,隐藏的这个hiberfil.sys文件大小正好和自己的物理内存是一致的,当你让电脑进入休眠状态时,Windows 7在关闭系统前将所有的内存内容写入Hiberfil.sys文件。   而后,当你重新打开电脑,操作系统使用Hiberfil.sys把所有信息放回内存,电脑

简单的Q-learning|小明的一维世界(3)

简单的Q-learning|小明的一维世界(1) 简单的Q-learning|小明的一维世界(2) 一维的加速度世界 这个世界,小明只能控制自己的加速度,并且只能对加速度进行如下三种操作:增加1、减少1、或者不变。所以行动空间为: { u 1 = − 1 , u 2 = 0 , u 3 = 1 } \{u_1=-1, u_2=0, u_3=1\} {u1​=−1,u2​=0,u3​=1}

简单的Q-learning|小明的一维世界(2)

上篇介绍了小明的一维世界模型 、Q-learning的状态空间、行动空间、奖励函数、Q-table、Q table更新公式、以及从Q值导出策略的公式等。最后给出最简单的一维位置世界的Q-learning例子,从给出其状态空间、行动空间、以及稠密与稀疏两种奖励函数的设置方式。下面将继续深入,GO! 一维的速度世界 这个世界,小明只能控制自己的速度,并且只能对速度进行如下三种操作:增加1、减

求空间直线与平面的交点

若直线不与平面平行,将存在交点。如下图所示,已知直线L过点m(m1,m2,m3),且方向向量为VL(v1,v2,v3),平面P过点n(n1,n2,n3),且法线方向向量为VP(vp1,vp2,vp3),求得直线与平面的交点O的坐标(x,y,z): 将直线方程写成参数方程形式,即有: x = m1+ v1 * t y = m2+ v2 * t

opengl纹理操作

我们在前一课中,学习了简单的像素操作,这意味着我们可以使用各种各样的BMP文件来丰富程序的显示效果,于是我们的OpenGL图形程序也不再像以前总是只显示几个多边形那样单调了。——但是这还不够。虽然我们可以将像素数据按照矩形进行缩小和放大,但是还不足以满足我们的要求。例如要将一幅世界地图绘制到一个球体表面,只使用glPixelZoom这样的函数来进行缩放显然是不够的。OpenGL纹理映射功能支持将