news 2026/7/8 12:48:26

【URP】Unity[法线贴图]原理与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【URP】Unity[法线贴图]原理与实践

解决的问题

‌性能优化‌:用低多边形模型配合法线贴图替代高模,减少计算开销

‌细节增强‌:通过RGB通道存储法线方向,模拟表面凹凸、划痕等微观结构

‌动态光照响应‌:每个像素的法线独立参与光照计算,实现更真实的明暗变化

历史发展节点

‌1998年‌:首次由Crytek在游戏《Far Cry》中大规模应用

‌2004年‌:成为DirectX 9标准特性,进入主流游戏引擎

‌2018年‌:Unity URP管线整合法线贴图标准化工作流,支持移动端优化

‌2022年‌:HLSL语法改进,分离纹理对象与采样器声明

生成与使用流程

生成方法

‌高模烘焙‌:通过ZBrush等工具将高模细节烘焙到低模法线贴图

‌程序生成‌:Substance Designer等工具从高度图转换生成

‌手动绘制‌:Photoshop使用滤镜生成基础法线纹理

详细存储原理参看了解具体如何计算和存储的。

URP实现步骤

‌纹理导入‌

类型设为Default,勾选Bump Map自动切换模式

压缩格式推荐BC5 (DXT5nm)或BC7

‌材质配置

Shader选择:URP > Lit 或 Simple Lit

法线贴图拖拽至Normal Map插槽

调整Normal Scale参数控制凹凸强度(0.5-1.5为常用范围‌

‌Shader核心原理‌

‌切线空间转换‌:通过TBN矩阵将法线从切线空间转到世界空间

‌光照计算‌:转换后的法线与光源方向点积决定漫反射强度

完整示例代码

以下URP Shader实现法线贴图与基础光照:

‌顶点着色器‌:计算世界空间法线和切线

‌片段着色器‌:采样法线贴图并通过TBN矩阵转换

‌光照模型‌:采用Lambert漫反射计算

NormalMapShader.shader

Shader "Custom/NormalMapShader" {

Properties {

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

_NormalMap("Normal Map", 2D) = "bump" {}

_NormalScale("Normal Scale", Range(0,2)) = 1

}

SubShader {

Tags { "RenderPipeline"="UniversalPipeline" }

HLSLINCLUDE

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

ENDHLSL

Pass {

HLSLPROGRAM

#pragma vertex vert

#pragma fragment frag

struct Attributes {

float4 positionOS : POSITION;

float2 uv : TEXCOORD0;

float3 normalOS : NORMAL;

float4 tangentOS : TANGENT;

};

struct Varyings {

float4 positionCS : SV_POSITION;

float2 uv : TEXCOORD0;

float3 normalWS : TEXCOORD1;

float4 tangentWS : TEXCOORD2;

};

sampler2D _MainTex;

sampler2D _NormalMap;

float _NormalScale;

Varyings vert(Attributes IN) {

Varyings OUT;

VertexPositionInputs posInput = GetVertexPositionInputs(IN.positionOS.xyz);

OUT.positionCS = posInput.positionCS;

OUT.uv = IN.uv;

VertexNormalInputs normInput = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);

OUT.normalWS = normInput.normalWS;

OUT.tangentWS = float4(normInput.tangentWS, IN.tangentOS.w);

return OUT;

}

half4 frag(Varyings IN) : SV_Target {

float4 normalSample = tex2D(_NormalMap, IN.uv);

float3 tangentNormal = UnpackNormalScale(normalSample, _NormalScale);

float3 normalWS = IN.normalWS;

float3 tangentWS = IN.tangentWS.xyz;

float3 bitangentWS = cross(normalWS, tangentWS) * IN.tangentWS.w;

float3x3 TBN = float3x3(tangentWS, bitangentWS, normalWS);

float3 finalNormal = mul(tangentNormal, TBN);

Light mainLight = GetMainLight();

float NdotL = saturate(dot(finalNormal, mainLight.direction));

half3 albedo = tex2D(_MainTex, IN.uv).rgb;

half3 diffuse = albedo * NdotL * mainLight.color;

return half4(diffuse, 1);

}

ENDHLSL

}

}

}

数据结构定义

Attributes结构体:声明顶点输入数据

positionOS:模型空间顶点位置

uv:纹理坐标

normalOS:模型空间法线

tangentOS:模型空间切线(含手性信息)

Varyings结构体:定义顶点到片段的传递数据

positionCS:裁剪空间位置

normalWS:世界空间法线(通过URP内置函数转换)

tangentWS:世界空间切线(保留手性分量)

顶点着色器实现

核心流程:

调用GetVertexPositionInputs转换模型空间到裁剪空间

通过GetVertexNormalInputs计算世界空间法线和切线

保持原始UV坐标传递

片段着色器实现

法线贴图处理:

float4 normalSample = tex2D(_NormalMap, IN.uv); float3 tangentNormal = UnpackNormalScale(normalSample, _NormalScale);

使用UnpackNormalScale函数解压法线贴图(范围从[0,1]映射到[-1,1])并应用强度参数。

TBN矩阵构建:

float3x3 TBN = float3x3(tangentWS, bitangentWS, normalWS); float3 finalNormal = mul(tangentNormal, TBN);

通过切向量、副法线和法线构建正交基,将切线空间法线转换到世界空间。

光照计算:

Light mainLight = GetMainLight(); float NdotL = saturate(dot(finalNormal, mainLight.direction)); half3 diffuse = albedo * NdotL * mainLight.color;

采用Lambert漫反射模型,计算法线与光源方向的点积作为光照强度因子。

关键函数说明

GetVertexPositionInputs:URP内置函数,处理顶点位置变换

UnpackNormalScale:URP提供的法线贴图解压函数

GetMainLight:获取场景主光源信息(需配合URP的Lightweight Render Pipeline使用)

小结

坐标空间转换:完整实现模型空间→世界空间→切线空间的转换链

光照模型:基于物理的简单漫反射计算

性能优化:使用half类型减少内存占用,适合移动端

扩展性:通过_NormalScale参数可动态调整法线贴图强度

实际项目应用

‌角色模型‌:增强皮肤皱纹或服装褶皱细节

‌环境场景‌:表现砖墙缝隙或金属表面划痕

‌性能权衡‌:移动端建议使用Simple Lit简化版着色器

关键注意事项:

确保模型具有正确的UV和切线数据

避免sRGB模式导入法线贴图

多光源场景需在Shader中添加额外光照循环

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/5 10:08:25

大模型面试经验汇总:22家大厂面试实录+高频考点解析

本文详细记录了作者2023年参加的22家科技公司大模型岗位面试经验,包括面试流程、问题及感悟。文章总结了高频考点:多头注意力机制、框架并行方式、主流模型细节、大模型训练技巧、数据预处理和模型评估等。作者指出大模型领域竞争激烈,岗位对…

作者头像 李华
网站建设 2026/7/7 16:55:38

Java计算机毕设之基于springboot的村务管理系统的设计与实现springboot+mysql村务档案管理系统设计与实现(完整前后端代码+说明文档+LW,调试定制等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华
网站建设 2026/7/1 15:07:10

Lock()与TryLock():并发锁机制核心差异

在共享内存或并发编程中,lock()与tryLock()是两种不同的锁获取机制,核心差异体现在阻塞行为、返回值、中断处理及适用场景上,具体分析如下:1. 阻塞行为lock():是阻塞式操作。若锁已被其他线程持有,调用线程…

作者头像 李华
网站建设 2026/7/5 1:27:21

springboot 分布式验证码登录的通用方案

为了防止世界被破坏,为了守护世界的和平。。。说错了,重来~ 为了防止验证系统被暴力破解,很多系统都增加了验证码效验,比较常见的就是图片二维码,业内比较安全的是短信验证码,当然还有一些拼图验证码&…

作者头像 李华