项目上要实现 Unity 真实瞄准镜的功能,其中之一就是反射式红点瞄准镜。本文分析的 Shader 以模拟反射式瞄准镜光学原理的方式实现了较为真实的效果。

效果图

该图展示了从不同角度观看红点瞄准镜的效果。

unity真实全息红点瞄准镜

红点瞄准镜原理

反射式红点瞄准镜的特殊之处在于不管眼睛和照门等是否三点一线,只要看到红点套在目标上,就代表已瞄准目标。

首先上一张灵魂插图:

上图表示了反射式红点瞄准镜最简单的组成部分,即光源+凹面镜,原理如下:

  1. 凹面镜为透明镜子,前方景物的反射光可以穿透。同时凹面镜有特殊镀膜,使得光源的光可以反射回去。

  2. 光源位于凹面镜的焦点,根据物理原理,其光线经过凹面镜反射后为平行光。

  3. 人眼接收到某些平行光时,可以脑补出无穷远处有一个红点。

  4. 由于光源的光线反射后为平行光,因此从侧面看,红点会发生偏移,当角度过大时,红点就完全看不到了。

反射式红点瞄准镜 Shader

代码

该 Shader 核心代码只有七行,其中最关键代码为前四行:

void surf (Input IN, inout SurfaceOutput o) {
    float shortestDistanceToSurface = dot (_WorldSpaceCameraPos - IN.worldPos,IN.worldNormal);
    float3 closestPoint = _WorldSpaceCameraPos - (shortestDistanceToSurface * IN.worldNormal);
    float2 uv_Delta = (mul((float3x3)unity_WorldToObject,IN.worldPos) - mul((float3x3)unity_WorldToObject,closestPoint)).xy * _uvScale;
    half4 col = tex2D(_reticleTex,(0.5f, 0.5f) + uv_Delta/shortestDistanceToSurface);
    o.Emission = (col.a * _reticleColour.rgb * _reticleBright);
    o.Albedo = max(col.a * _reticleColour.rgb, _glassTrans * _glassColour.rgb);
    o.Alpha = max(col.a, _glassTrans);
}

解析

  1. 找到离平面最近距离及交点
    首先找到摄像机到镜片表面的最近距离,用点乘计算,得shortestDistanceToSurface。计算摄像机到镜片平面最短路径的接触点,得closestPoint(不考虑 z 轴)。
  2. 计算红点贴图采样偏移
    根据上文瞄准镜原理可知,在物理世界中,反射的红点光线均为平行光,因此若最短路径接触点在镜片区域中,则该点一定是红点图案的中心
    在本地坐标下,计算closestPoint与本顶点位置的偏移值,该值只取 x 和 y,该数值的意义是:当前顶点相对于红点纹理中心的偏移量。
    将偏移量乘上一个用于调节红点图案缩放的可调变量_uvScale,之后使用 tex2D 采样,其中 (0.5f,0.5f) 为红点纹理的中心,加上偏移量,即为该顶点的纹理采样。
    这里使用 uv_Delta/shortestDistanceToSurface的目的是使得图案在正常范围内尽量不随距离改变而改变。也就是说,希望不管距离镜片多远,虚拟红点大小都一致,没有近大远小的透视效果。
    当然该除法并不严谨,因为物体所占的视场角大小不仅和距离摄像机距离有关,还和物体与摄像机中心的偏移量、摄像机视场角有关。但是该除法在实际使用场景用已经足够了,在可视范围内图案大小变化不是特别明显。
  3. 设置顶点参数
    之后,将采样得到的颜色按需做些特殊处理,最后设置顶点的颜色等参数。

关于 VR

若使用 ShaderLab 中的表面着色器,一般情况下 Unity 会自动处理左右眼的渲染,即_WorldSpaceCameraPos的值会根据当前渲染的眼睛发生变化。因此,我们无需手动计算左右眼的情况。

若使用普通的顶点着色器或者片元着色器,则可能需要判断当前渲染的眼睛来设置特定参数值。Unity 提供了渲染眼睛状态判断以及相应的瞳距等参数,对应取值判断并计算即可。

梓喵出没博客(azimiao.com)版权所有,转载请注明链接:https://www.azimiao.com/7125.html
欢迎加入梓喵出没博客交流群:313732000

我来吐槽

*

*