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

来源:互联网 发布:spss软件安装 编辑:程序博客网 时间:2024/06/05 18:30

原文 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)

阅读全文
0 0