【Unity Shaders】法线纹理(Normal Mapping)的实现细节 笔记

本文详细解析了Unity引擎中对于非均匀缩放情况下的法线变换处理方式,探讨了统一缩放和非均匀缩放下物体坐标到世界坐标的转换,并对比了不同方法下的效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文 http://blog.csdn.net/candycat1992/article/details/41605257


Unity4 版本中Unity对模型非同一缩放的情况进行了特殊处理:


_Object2World
is the transformation from object coordinates to world coordinates.
_World2Object * unity_Scale.w
is the transformation from world coordinates to object coordinates.
Unity sometimes (for non-uniform scale factors) scales the vertex coordinates before handing them to the shader. (Supposedly such that _Object2World is an orthogonal matrix.) For uniform scale factors, however, it doesn't do this vertex scaling but integrates the scaling into _Object2World (making it a scaled orthogonal matrix). However, for some strange reason (and this is probably an inconsistency that just never got fixed and now too much code relies on it) this scaling was never included in _World2Object. Thus, you have to scale _World2Object with unity_Scale.w (if scale is important) but _Object2World is already scaled with the reciprocal factor.

No the space is the same, but in a general case (not unity) a non orthogonal matrix doesn't transform normals correctly.


If you want to perform a correct normal transformation in the general case you need to use theinverse transpose matrix. (In unity shaders this is equivalent tomul(normal,_World2Object).


_Object2World works like a charm :(mul(_Object2World, normal)). both rotation and scaling (uniformly) gave the expected matrix through color checking elements in rows and columns


This is a bit tricky to explain (I hope to remember it correctly).You can assumescale is always uniform because Unity(at least until 4.x version. I think it will change in 5.x) doesn't apply non-uniform scale in the vertex shader but pre-transform the mesh CPU side.


Following uniforms are provided to shaders:


_Object2World: contains the world matrix including the scale
unity_Scale: the w component contains the inverse of the uniform scale factor (w = 1/scale)
_World2Object: contains the inverse world matrix without scale
In order to correctly transform the normal from object to world space, you have 3 possibility:


transform the scaled normal : float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
you can avoid to use the scaled normal if you normalize the normal after the transform (probably this is your case otherwise AFAIK the transformation shouldn't be 100% correct)
use the inverse transpose : mul(normal,_World2Object)
SCALED_NORMAL is defined this way:


#define SCALED_NORMAL (v.normal * unity_Scale.w)


vertex shader
float3 worldView = mul ((float3x3)_Object2World, -ObjSpaceViewDir(v.vertex));    
o.TtoW0 = float4(mul(rotation, _Object2World[0].xyz), worldView.x) * unity_Scale.w;    
o.TtoW1 = float4(mul(rotation, _Object2World[1].xyz), worldView.y) * unity_Scale.w;    
o.TtoW2 = float4(mul(rotation, _Object2World[2].xyz), worldView.z) * unity_Scale.w; 


pixel shader:
fixed3 worldRefl = reflect (worldView, half3(dot(i.TtoW0, norm), dot(i.TtoW1, norm), dot(i.TtoW2, norm)));  


这里法线变换的代码还是很难理解。


我尝试分析一下:


1. 首先有一处小错误:worldView 应该不需要乘以 unity_Scacle.w,而且可以调用 WorldSpaceViewDir 直接得到。

2. 第二点 _Object2World[0] 获取的是 _Object2World 的第一行(注意不是列!),查看矩阵乘法的定义,这里显然不是 mul(rotation, _Object2World) 的意思,

注意通过 _Object2World[0].xyz * unity_Scale.w 缩放系数被移除了,
_Object2World * unity_Scale.w 的转置矩阵 == _World2Object,
所以此处的含义是 mul(rotation, _World2Object) 得到 WorldToTangent 矩阵


3. 第三点 TtoW0, TtoW1, TtoW2 保存的是 WorldToTangent 变换矩阵的1,2,3列(注意不是行!)
结合PixelShader中的写法
fixed3 worldRefl = reflect (worldView, half3(dot(i.TtoW0, norm), dot(i.TtoW1, norm), dot(i.TtoW2, norm))); 
pixelShader中实际上做的是 mul(normal, WorldToTangent),
注意通过 _Object2World[0].xyz * unity_Scale.w 缩放系数被移除了,WorldToTangent 只包含旋转变换,是正交矩阵;
因此 WorldToTangent 的转置矩阵 == TangentToWorld,
所以  mul(normal, WorldToTangent) == mul(TangentToWorld, normal)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值