尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Unity3D Shader 法线与基础光照

Unity3D Shader 法线与基础光照
📅 发布时间:2026/8/1 3:37:53

Unity3D Shader 法线与基础光照

前言

前面的文章里,我们已经反复提到“法线”这个词。

比如:

  • 顶点法线外扩做描边
  • 光照方向和法线方向做点乘
  • 法线贴图会改变表面受光表现

那法线到底是什么?

为什么一旦涉及光照,几乎就一定要用到法线?

这一篇,我们从最基础的角度出发,讲清楚:

  • 法线是什么
  • 光照为什么和法线有关
  • dot点乘在光照里代表什么
  • 如何写一个最基础的 Lambert 漫反射 Shader

什么是法线(Normal)

法线可以简单理解为:

一个表面“朝向哪里”的方向。

它通常是一个单位向量。

例如一个朝上的平面,它的法线可能是:

(0,1,0)

也就是说,这个面朝向 Y 轴正方向。

为什么法线和光照有关

因为光照最基本的问题就是:

光线打到这个表面时,它到底是正面迎光,还是斜着迎光,还是背光?

而判断这件事,最直接的方法就是:

  • 看表面的法线方向
  • 看光照方向

这两个方向越一致,说明表面越正对光源,通常就越亮。

越偏离,通常就越暗。

dot 点乘为什么这么重要

在 Shader 光照中,最常见的一行代码就是:

dot(normal,lightDir)

点乘可以理解成:

两个方向有多接近。

它的结果范围大致是:

  • 1:完全同方向
  • 0:互相垂直
  • -1:完全相反

在光照里通常会写成:

saturate(dot(normal,lightDir))

意思是:

  • 小于 0 的部分直接压成 0
  • 大于 1 的部分压成 1

这样就得到一个 0~1 的受光强度。

最基础的漫反射思想

最经典的基础光照模型之一,就是Lambert 漫反射。

它的核心就是:

diffuse=max(0,dot(N,L))

其中:

  • N:法线方向(Normal)
  • L:光照方向(Light Direction)

当表面正对光源时:

  • dot接近 1
  • 物体更亮

当表面转到侧面时:

  • dot接近 0
  • 物体更暗

这就是最基础的“明暗变化”。

在 Shader 中如何拿到法线

Mesh 顶点输入里,通常这样声明:

float3 normal:NORMAL;

例如:

structappdata{float4 vertex:POSITION;float3 normal:NORMAL;};

这表示从模型数据中读取法线。

但要注意:

这里的法线通常是对象空间法线。

而做光照时,我们通常更常用世界空间法线。

因此往往要转换一下:

float3 worldNormal=UnityObjectToWorldNormal(v.normal);

为什么光照计算常用世界空间

因为:

  • 灯光方向通常在世界空间下更容易理解
  • 摄像机位置是世界空间
  • 物体在场景中的朝向变化,也更方便统一计算

只要参与同一次运算的数据都在同一个空间,结果才是可靠的。

一个最基础的 Lambert 光照 Shader

下面写一个最基础的版本:

  • 支持主方向光
  • 包含环境光 + 漫反射
  • 使用法线和光照方向做点乘
Shader"Custom/SimpleLambert"{Properties{_Color("Main Color",Color)=(1,1,1,1)}SubShader{Pass{Tags{"LightMode"="ForwardBase"}CGPROGRAM#pragmavertex vert#pragmafragment frag#pragmamulti_compile_fwdbase#include"UnityCG.cginc"#include"Lighting.cginc"structappdata{float4 vertex:POSITION;float3 normal:NORMAL;};structv2f{float4 pos:SV_POSITION;float3 worldNormal:TEXCOORD0;};fixed4 _Color;v2fvert(appdata v){v2f o;o.pos=UnityObjectToClipPos(v.vertex);o.worldNormal=UnityObjectToWorldNormal(v.normal);returno;}fixed4frag(v2f i):SV_Target{fixed3 N=normalize(i.worldNormal);fixed3 L=normalize(_WorldSpaceLightPos0.xyz);fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*_Color.rgb;fixed ndotl=saturate(dot(N,L));fixed3 diffuse=_LightColor0.rgb*_Color.rgb*ndotl;returnfixed4(ambient+diffuse,_Color.a);}ENDCG}}}

代码拆解

1 从模型里读取法线

float3 normal:NORMAL;

2 把法线转到世界空间

o.worldNormal=UnityObjectToWorldNormal(v.normal);

3 获取主光方向

fixed3 L=normalize(_WorldSpaceLightPos0.xyz);

这里我们主要讨论方向光(Directional Light)的基础情况。

4 计算漫反射强度

fixed ndotl=saturate(dot(N,L));

这一步就是核心:

  • 法线越朝向光源 → 值越大
  • 法线越背离光源 → 值越小

5 乘上灯光颜色和材质颜色

fixed3 diffuse=_LightColor0.rgb*_Color.rgb*ndotl;

6 加上一点环境光

fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*_Color.rgb;

如果没有环境光,背光面会黑得太死。

效果会是什么样

这个 Shader 的效果特点是:

  • 朝向光源的一面更亮
  • 背向光源的一面更暗
  • 比纯色 Unlit 更有体积感

这就是最基础的“受光”感觉。

Half-Lambert:让阴影面别那么黑

标准 Lambert 在某些卡通或风格化场景里,会显得阴影过硬。

一个常见改法是:

floathalfLambert=dot(N,L)*0.5+0.5;

再做一次限制:

halfLambert=saturate(halfLambert);

这样背光面不会一下掉到纯黑,会显得更“柔和”。

很多早期卡通渲染教程都会用这种方法。

法线方向不对会发生什么

如果法线错误,最直接的表现就是:

  • 明暗反了
  • 某些面不该亮却很亮
  • 旋转模型后受光异常

所以当你发现光照不对时,除了看代码,也要怀疑:

  • 模型法线是否正确
  • 空间是否统一
  • 有没有忘记normalize

为什么几乎总要normalize

因为法线和光照方向参与点乘时,我们通常希望比较的是“方向”,而不是长度。

所以会习惯这样写:

fixed3 N=normalize(i.worldNormal);fixed3 L=normalize(_WorldSpaceLightPos0.xyz);

不归一化虽然有时“看起来也能跑”,但结果可能会失真。

常见坑

1 法线和光照方向不在同一个空间

例如:

  • 法线在对象空间
  • 光照方向在世界空间

直接点乘就是错的。

2 忘了归一化

点乘结果会不稳定。

3 以为法线就是顶点位置方向

法线是“表面朝向”,不是顶点坐标。

4 背光面全黑,以为 Shader 写错了

很多时候只是因为你只写了 Lambert,没加环境光。

总结

这一篇最重要的核心只有一句:

基础光照,本质上就是“表面朝向”和“光照方向”的关系。

在 Shader 中最关键的公式就是:

saturate(dot(N,L))

你可以先记住下面这几个点:

  • NORMAL用来接收模型法线
  • 光照计算常用世界空间法线
  • dot(N, L)表示受光强度
  • Lambert是最基础的漫反射模型
  • 环境光可以避免背光面死黑

后面你继续学:

  • Blinn-Phong 高光
  • Fresnel
  • 法线贴图
  • PBR

其实都是在这个基础上不断往上加东西。

相关新闻

  • 最少转弯路径算法:从BFS到0-1 BFS与Dijkstra的优化实践
  • AI对话应用增长策略:从工具到生态的演进与商业化设计
  • Windows平台B站第三方客户端终极指南:免费开源BiliBili-UWP完全使用教程

最新新闻

  • C++右值引用与移动语义:从原理到实战的性能优化指南
  • PTP报文格式深度解析:从协议原理到抓包排错实战
  • 华三交换机ACL配置实战:基于IP网段实现内网访问控制与安全隔离
  • Meta Quest 3混合现实开发实战:手部追踪与场景锚点构建沉浸式MR应用
  • 【Bug已解决】[Feature request] Support already-sharded DataLoaders in Accelerator.prepare 解决方案
  • UART串口通信波形全解析:从起始位到停止位,掌握嵌入式调试核心技能

日新闻

  • ClickHouse版本管理深度实战:4步构建零风险升级与回滚体系
  • Java 23 种设计模式:从踩坑到精通 | 番外:责任链模式 —— 物流审批流程实战
  • 华硕笔记本性能解放指南:G-Helper轻量级控制工具全面解析

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • ClickHouse版本管理深度实战:4步构建零风险升级与回滚体系
  • Java 23 种设计模式:从踩坑到精通 | 番外:责任链模式 —— 物流审批流程实战
  • 华硕笔记本性能解放指南:G-Helper轻量级控制工具全面解析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号