UnityObjectToWorldDir和UnityObjectToWorldNormal的区别

UnityObjectToWorldDir用于把模型空间下的矢量转换到世界空间

UnityObjectToWorldNormal用于把模型空间下的法线向量转换到世界空间。因为必须保证法线垂直于模型的表面,所以缩放的时候与普通矢量不一样。

如果法线用UnityObjectToWorldDir,则会出现以下错误

而用UnityObjectToWorldNormal,则可得到正确的结果

 

 

详细解释如下(转自catlike coding)

世界坐标空间中的法线

 

除了被动态批次合并的对象以外,我们所有的法线都在物体空间之中。但是我们必须知道世界坐标空间中的表面的方向。因此,我们必须将法线从物体空间转换到世界坐标空间。为了做到这一点,我们需要物体的转换矩阵信息。

 

Unity将一个物体的整个变换层次结构折叠成一个单一的变换矩阵,就像我们在第一部分中所做的那样。我们可以将它写为O = T1T2T3 ...其中T是单独的变换矩阵,而O是组合变换矩阵。这个矩阵被称为物体空间到世界空间的变换矩阵。

 

Unity通过类型为float4x4的unity_ObjectToWorld变量使这个矩阵在着色器中可用,该变量在UnityShaderVariables中进行定义。将这个矩阵乘以顶点着色器中的法线数据,以便将数据转换到世界坐标空间。因为它是一个方向,重新定位应该被忽略。所以齐次坐标的第四个分量必须为零。

 

 

或者,我们可以只对矩阵的3×3的部分做乘法运算。编译出来的代码最终是一样的,因为编译器会去掉所有与常数零相乘的东西。

 

 

从物体空间变换到世界坐标空间。

 

法线现在处于世界坐标空间,但有些发现看起来比别的发现更亮。这是因为他们也进行了缩放。因此,我们必须在转换后对法线进行归一化。

 

 

归一化后的法线。

 

虽然我们再次对向量进行了归一化,但对于没有均匀大小的对象来说,它们看起来很奇怪。这是因为当表面在一个维度上进行拉伸的时候,这个表面的法线不会以相同的方式进行拉伸。

 

在X轴进行缩放,顶点和法线都变为½。

 

当大小不均匀的时候时,应该对法线进行取逆操作。这样,当它们被再次被归一化后,法线将匹配变形的曲面的形状。而这对于均匀尺度来说没有影响。

在X轴进行缩放,顶点变为½,而法线加倍。

 

所以我们必须对大小进行取逆操作,但旋转应该保持不变。那么我们应该怎么做?

 

我们将对象的变换矩阵描述为O = T1T2T3 ...但我们可以更加具体一些。我们知道层次结构中的每个步骤都结合了缩放、旋转和位移。因此每个T可以分解为SRP。

 

这意味着 O=S1R1P1S2R2P2S3R3P3…,但是为了方便起见,让我们假设说 O=S1R1P1S2R2P2 。

 

因为法线是方向向量,所以我们不关心重新定位的问题。所以我们可以进一步简化到O=S1R1S2R2,而且我们只需要考虑3×3的矩阵。

 

我们想要对缩放取逆,但同时保持旋转不变。所以我们想要一个新的矩阵N = S-11R1S-12R2。

 

如何对矩阵取逆?

 

矩阵M的逆写作。 它也是一个矩阵,当它们相乘的时候,将抵消另外一个矩阵带来的操作。互相是对方矩阵的逆。所以

 

要抵消一系列步骤带来的影响,必须以相反的顺序执行相反的步骤。这方面的助记符涉及一些规则。这意味着

 

对于单个数x的情况,它的逆更加简单的 ,这是因为 。这也表明零没有逆元。也不是每个矩阵都具有相应的逆矩阵。

我们正在使用缩放、旋转和重新定位矩阵。只要我们不把矩阵缩放为零,所有这些矩阵可以取逆。

 

位移矩阵的逆矩阵是通过简单地对其第四列中的XYZ分量取负来得到的。

 

 

缩放矩阵的逆矩阵是通过对它的对角线上的分量取倒数得到的,我们只需要考虑3×3的矩阵。

 

 

旋转矩阵可以每次针对一个轴进行考虑,例如考虑围绕Z轴的情况。 旋转z弧度的操作可以通过简单旋转-z弧度的操作来抵消。当你研究正弦和余弦波的时候,你会注意到sin(-z)= - sinz和cos(-z)= cosz。 这使得旋转矩阵的逆矩阵非常简单。

 

 

需要注意的是,旋转矩阵的的逆矩阵在其主对角线上的分量与原始矩阵相同。只有正弦分量的正负发生了变化。

 

除了物体空间到世界空间的变换矩阵意外,Unity还提供了一个世界空间到物体空间的变换矩阵。这些矩阵实际上是彼此的逆矩阵。所以我们得到这么一个公式

 

这给出了我们需要的缩放矩阵的逆矩阵,但也给了我们旋转矩阵和位移矩阵的逆矩阵。幸运的是,我们可以通过转置矩阵来移除那些我们不需要的效果。 然后我们得到

 

什么是矩阵的转置?

 

矩阵M的转置被写为。通过翻转矩阵的主对角线上的变量来对矩阵进行转置。因此它的行会成为转置矩阵的列,它的列会成为转置矩阵的行。需要注意的是,这意味着对角线上的变量本身保持不变。

像逆矩阵一样,对矩阵乘法进行转置会反转其顺序。。  当对不是方阵的矩阵使用的时候,这是有意义的,否则可能会导致无效的乘法。 但是一般来说,这个等式是成立的,你可以查找下它的证明。

 

当然转置两次会让你得到最初的结果。所以 

 

所以,让我们转置世界空间到物体空间的矩阵,并乘以顶点的法线数据。

 

 

正确的世界坐标空间的法线。

 

实际上,UnityCG包含一个方便的UnityObjectToWorldNormal函数,正是做这个工作。所以我们可以使用那个函数。它也使用显式的矩阵乘法,而不是使用矩阵转置。这应该会生成更好的编译代码。

 

 

UnityObjectToWorldNormal看起来是什么样子?

 

这里就是UnityObjectToWorldNormal的代码了。 inline关键字不起任何作用。

 

 

重新归一化

 

在顶点程序中产生正确的法线之后,正确的法线值会通过内插值器。不幸的是,在不同单位长度的向量之间进行线性内插不会生成另外一个单位长度的向量。它会比单位长度的向量要小一些。

所以我们必须在片段着色器中再次对法线进行归一化。

 

 

对法线重新进行归一化。

 

虽然对法线重新进行归一化可以产生更好的结果,但这两者之间的误差通常非常小。如果你更重视性能的话,你可以决定不在片段着色器里面再次进行归一化。这是移动设备中常见的优化。

 

这是比较夸张的错误。

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 实时折射 Shader 是在渲染透明物体时,通过计算物体内部的折射光线,使得物体表面呈现出折射的效果。以下是一个简单的 Unity 实时折射 Shader 示例: ```shader Shader "Custom/Refraction" { Properties { _MainTex ("Texture", 2D) = "white" {} _Refraction ("Refraction", Range(0.0, 1.0)) = 1.0 _BumpMap ("Normalmap", 2D) = "bump" {} _RefractionColor ("Refraction Color", Color) = (1,1,1,1) } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 texcoord : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; float3 worldRefract : TEXCOORD3; float3 worldView : TEXCOORD4; float3 worldIncident : TEXCOORD5; float3 worldTangent : TEXCOORD6; float3 worldBinormal : TEXCOORD7; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; sampler2D _BumpMap; float4 _MainTex_ST; float _Refraction; float3 _WorldSpaceCameraPos; float4x4 unity_WorldToObject; float4x4 unity_ObjectToWorld; float4x4 unity_CameraToWorld; float4x4 unity_WorldToCamera; float3 TransformWorldToTangent(float3 worldVec, float3 worldNormal, float3 worldTangent, float3 worldBinormal) { float3 tangentVec = float3(dot(worldVec, worldTangent), dot(worldVec, worldBinormal), dot(worldVec, worldNormal)); return tangentVec; } void vert (inout appdata v) { UNITY_INITIALIZE_OUTPUT(appdata, v); float3 worldNormal = UnityObjectToWorldNormal(v.normal); float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; float3 worldPos = UnityObjectToWorld(v.vertex); float3 worldView = Normalize(_WorldSpaceCameraPos - worldPos); float3 worldIncident = TransformWorldToTangent(-worldView, worldNormal, worldTangent, worldBinormal); v.worldNormal = worldNormal; v.worldPos = worldPos; v.worldIncident = worldIncident; v.worldView = worldView; v.worldTangent = worldTangent; v.worldBinormal = worldBinormal; v.uv = TRANSFORM_TEX(v.texcoord, _MainTex); UNITY_TRANSFER_FOG(v, float4(v.vertex.xyz, 1)); } float3 Refract(float3 I, float3 N, float eta) { float cosi = dot(I, N); float cost2 = 1.0 - eta * eta * (1.0 - cosi * cosi); return cost2 > 0.0 ? eta * I - (eta * cosi + sqrt(cost2)) * N : float3(0, 0, 0); } float4 frag (v2f i) : SV_Target { float3 normal = UnpackNormal(tex2D(_BumpMap, i.uv)); float3 worldNormal = normalize(i.worldNormal); float3 worldTangent = normalize(i.worldTangent); float3 worldBinormal = normalize(i.worldBinormal); float3 worldIncident = normalize(i.worldIncident); float3 worldRefract = Refract(worldIncident, worldNormal, 1.0 / _Refraction); float2 refractOffset = i.uv + worldRefract.xy * (_Refraction * 0.01); float4 refractColor = tex2D(_MainTex, refractOffset); float3 viewDir = normalize(i.worldView); float fresnel = pow(1.0 - dot(-viewDir, worldNormal), 5.0); float3 refractTint = lerp(float3(1, 1, 1), _RefractionColor.rgb, _RefractionColor.a); float3 color = refractColor.rgb * refractTint; return float4(color * fresnel, refractColor.a); } ENDCG } } FallBack "Diffuse" } ``` 上述代码中,我们首先定义了一些属性,包括主纹理、折射系数、法线贴图、折射颜色等。然后我们定义了两个结构体,一个用于传递顶点数据,一个用于传递片元数据。在顶点着色器中,我们计算了物体表面的法向量、切线、副切线、视线和入射光线等信息,并将其保存在传递顶点数据的结构体中。在片元着色器中,我们首先使用法线贴图计算物体表面的法向量,然后根据折射系数计算物体内部的折射光线,并将其应用到主纹理上,最后计算菲涅尔反射系数,并将其与折射颜色混合,得到最终的颜色。 注意:上述代码仅为示例代码,实际使用时还需要根据场景和需求进行相应的调整和优化。 ### 回答2: Unity 实时折射shader是一种用于实时渲染中模拟物体折射效果的技术。通过这种shader,可以使物体在光线穿过或经过物体时发生折射,从而产生逼真的光学效果。 实时折射shader的实现原理是基于折射率。每个物体都有一个折射率,折射率决定了光线在物体中传播时的速度和角度的改变。shader根据光线从场景中的其他物体射出时发生的折射,来计算物体表面上的折射效果。这个计算可以在真实时间内进行,因此称之为实时折射。 实时折射shader包括两个主要部分:反射和折射。反射是物体表面产生镜面反射的效果,折射是光线穿过物体并改变方向的效果。在shader代码中,通过计算光线的入射方向和折射方向之间的角度差,来确定反射和折射的强度和方向。使用非常接近真实光学效果的折射模型,可以使物体看起来更加真实。 在Unity中实现实时折射shader,我们通常使用一些现成的shader库和工具,例如Amplify Shader Editor等。这些工具提供了图形界面和预设功能,使得创建和调整折射shader变得更加容易。我们可以通过调整折射率和材质的属性,改变折射效果的强度和形态。同时,还可以结合其他效果,如反射、环境光遮蔽等,来进一步增强折射效果的真实感。 总之,Unity实时折射shader是一种模拟物体折射效果的技术,通过计算光线的折射和反射来产生真实的光学效果。通过使用现成的shader库和工具,我们可以在Unity中轻松实现和调整这种效果,使得物体的渲染更加逼真和吸引人。 ### 回答3: Unity 实时折射着色器是一种在Unity引擎中使用的效果,主要用于在游戏或应用程序中实现物体对光线的折射效果。折射效果是指当光线从一个介质进入另一个介质时,光线的传播方向发生改变的现象。 在Unity中实现实时折射效果需要使用着色器语言编写自定义的着色器。着色器是一种控制对象在屏幕上渲染的方式的程序,它可以对光照、材质和纹理进行处理。 实时折射着色器的实现通常分为以下几个步骤: 1. 计算入射光线:首先需要计算从光源到物体表面的光线矢量,这可以通过在着色器中使用光照模型和材质属性来完成。 2. 计算折射光线:根据物体表面的法线和折射率,可以使用数学公式计算出光线从一个介质到另一个介质的折射光线矢量。折射光线的计算需要考虑入射角和材质的折射率。 3. 折射纹理采样:为了模拟物体的折射效果,通常需要将折射纹理与折射光线相结合。通过在着色器中采样和使用折射纹理,可以在物体内部产生扭曲和变形的视觉效果。 4. 渲染:最后,使用计算得到的折射光线和纹理数据,将物体渲染到屏幕上。折射光线的方向和强度会影响物体的透明度和光线的散射效果。 通过使用Unity实时折射着色器,可以在游戏或应用程序中实现更加真实的光线和材质效果。这可以用来创建逼真的水面、玻璃、水晶等物体,并为用户提供更加沉浸式的视觉体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值