Unity Shader中各种空间及变换方法

前几天尝试写一个传送门的shader,发现自己对坐标之间的变换掌握的不够熟练,趁着这阵子想整理shader相关的知识点,先把各种空间及之间转换整理一下。

1 模型空间-世界空间-观察空间-裁剪空间

建模时在模型空间进行,模型自带的坐标均为模型空间下的表示。
当模型被放到世界坐标系中时,表达某个模型的位置使用的是世界空间下的坐标,所以模型上对应的某一个点,必须相应的转化为世界空间下的坐标。
从模型空间到世界空间的变换 叫做 模型变换
Unity的Shader中,模型空间的坐标由Renderer直接提供,作为顶点着色器的输入,语义为POSITION。如:

struct appdata
{
    float4 vertex : POSITION;
}

由于我们能看到的渲染图像均是通过摄像机得到的,为了方便后续裁剪、投影等操作,所以在将模型从模型空间变换到世界空间之后,还需要将其转换到观察空间。所谓的观察空间,就是以摄像机位置为原点,摄像机局部坐标轴为坐标轴的坐标系。
从世界空间到观察空间的变换叫做观察变换(视图变换)。

坐标转换到观察空间后,由于直接使用摄像机的平截头体进行裁剪比较复杂(平截头体的边界方程求交困难),所以需要将其转化到裁剪空间。裁剪空间变换的思路是,对平截头体进行缩放,使近裁剪面和远裁剪面变成正方形,使坐标的w分量表示裁剪范围,此时,只需要简单的比较x,y,z和w分量的大小即可。
从观察空间到裁剪空间的变换叫做投影变换
注意,虽然叫做投影变换,但是投影变换并没有进行真正的投影。

在顶点着色器中,模型、观察、裁剪空间的相关变换矩阵一般为以下几个:

别名定义含义
UNITY_MATRIX_Munity_ObjectToWorld模型变换矩阵
UNITY_MATRIX_Vunity_MatrixV视图变换矩阵
UNITY_MATRIX_Pglstate_matrix_projection投影变换矩阵
UNITY_MATRIX_VPunity_MatrixVP视图投影变换矩阵
UNITY_MATRIX_MVmul(unity_MatrixV, unity_ObjectToWorld)模型视图变换
UNITY_MATRIX_MVPmul(unity_MatrixVP, unity_ObjectToWorld)模型视图投影变换

注意,最新版本中(笔者用的是Unity5.6.3p4),官方建议将坐标点从模型空间转换到裁剪空间时,应使用UnityObjectToClipPos方法,该方法内部定义为:

mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0));

上述方法比起下面的顺序更有效率。

mul(UNITY_MATRIX_MVP,  appdata.vertex);

在定点着色器中,输出即为裁剪空间下的坐标。

2 屏幕空间

经过裁剪操作之后,我们需要将裁剪空间的坐标投影到屏幕空间。
这里主要分成两个步骤:
1. 齐次除法
使用xyz分别处以w分量,得到NDC(归一化设备坐标),经过这一步,能够看到的坐标点变成了一个xy边长为1,z为-1到1的立方体。
2. 屏幕映射
使用NDC坐标和屏幕长宽像素直接映射,得到屏幕空间下的xy坐标。注意,虽然屏幕空间没有深度,但屏幕空间下的坐标仍然保留了z的深度值,可以进行深度检测或其他处理。

从裁剪空间到屏幕空间由unity直接进行。这里还要记住,从裁剪空间到屏幕空间,插值是硬件直接进行的。

之后我们从像素着色器中获得的语义为SV_POSITION的输入,其坐标基本没有太多用处了。
如果希望获得此时的屏幕坐标,可以使用VPOS或者WPOS,在Unity中,这两者同义。当然,使用上述语义需要

#pragma target 3.0

VPOS中的xy代表屏幕空间中的像素坐标,注意,这里的像素坐标是中心点坐标,不是整数,如屏幕分辨率为400*300,则x的范围为[0.5, 400.5],y的范围为[0.5, 300.5]。z的范围为[0, 1],0是近裁剪面,1是远裁剪面。w的范围需要根据摄像机投影类型划分,如果是透视投影,则其范围为[1/near,1/far],如果是正交投影,则恒为1。
这里还需要注意的是,如果使用VPOS或者WPOS作为fs的输入,就无法同时使用SV_POSITION作为输入,所以vs和fs需要写成如下方式:

Shader "Unlit/Screen Position"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            // note: no SV_POSITION in this struct
            struct v2f {
                float2 uv : TEXCOORD0;
            };

            v2f vert (
                float4 vertex : POSITION, // vertex position input
                float2 uv : TEXCOORD0, // texture coordinate input
                out float4 outpos : SV_POSITION // clip space position output
                )
            {
                v2f o;
                o.uv = uv;
                outpos = UnityObjectToClipPos(vertex);
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
            {
                // screenPos.xy will contain pixel integer coordinates.
                // use them to implement a checkerboard pattern that skips rendering
                // 4x4 blocks of pixels

                // checker value will be negative for 4x4 blocks of pixels
                // in a checkerboard pattern
                screenPos.xy = floor(screenPos.xy * 0.25) * 0.5;
                float checker = -frac(screenPos.r + screenPos.g);

                // clip HLSL instruction stops rendering a pixel if value is negative
                clip(checker);

                // for pixels that were kept, read the texture and output it
                fixed4 c = tex2D (_MainTex, i.uv);
                return c;
            }
            ENDCG
        }
    }
}

这里写图片描述

摄像机和屏幕相关常用的内置变量如下表所示。

别名类型含义
_WorldSpaceCameraPosfloat3
_ProjectionParamsfloat4
_ScreenParamsfloat4
_ZBufferParamsfloat4
unity_OrthoParamsfloat4
unity_CameraProjectionfloat4x4
unity_CameraInvProjectionfloat4x4
unity_CameraWroldClipPlanes[6]float4

3 视口空间

熟悉OpenGL编程的同学都知道gl接口有一个glViewport,该接口其实就是确定视口空间。视口空间是一个将屏幕坐标对应到(0, 0)到(1, 1)范围内的空间。
如果想得到视口空间坐标,则可以通过下面两种方法。

fixed4 frag(float4 sp : VPOS) : SV_Target {
    return fixed(sp.xy/_ScreenParams.xy, 0.0, 1.0);
}

这里的_ScreenParams中保存了屏幕分辨率。
另一种方法如下:

struct vertOut {
    float4 pos : SV_POSITION;
    float4 srcPos : TEXCOORD0;
};

vertOut vert(appdata_base v) {
    vertOut o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.srcPos = ComputeScreenPos(o.pos);
    return o;
}

fixed4 frag(vertOut i) : SV_Target {
    float2 wcoord = (i.srcPos.xy/i.scrPos.w);
    return fixed4(wcoord, 0.0, 1.0);
}

这里,ComputeScreenPos并没有直接进行透视除法,原因是插值是线性的,必须在透视除法之后进行,所以,我们必须在fs中手动进行。

几篇不错的参考资料如下:
http://blog.csdn.net/wangdingqiaoit/article/details/51594408
http://blog.csdn.net/wangdingqiaoit/article/details/51589825

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Shader的乘法指的是矢量和矩阵之间的运算。在Shader,我们可以使用矩阵来表示一些空间变换,比如旋转、缩放和平移等。将矢量与矩阵相乘可以实现对矢量进行这些变换操作。 矢量和矩阵的联系主要是为了让矢量能够像一个矩阵一样参与矩阵运算。通过矩阵和矢量的乘法,我们可以将矢量应用到矩阵所表示的空间变换。这在进行三维渲染空间变换非常有用。 在Shader,还存在正交矩阵的概念。如果一个方阵M与它的转置矩阵的乘积等于单位矩阵,那么我们称这个矩阵是正交的。反之亦然。正交矩阵在进行空间变换时具有一些特殊的性质,可以保持向量的长度和角度。 在UnityShader,最常使用的是3x3和4x4的方阵,也称为方块矩阵。这些矩阵通常用来表示三维空间变换,比如模型矩阵、视图矩阵和投影矩阵等。通过矩阵和矢量的乘法,我们可以对顶点进行变换,实现模型的旋转、缩放和平移等操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [《Unity Shader入门精要》Shader所需的数学基础(三)](https://blog.csdn.net/qq_29198531/article/details/111039727)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值