如何计算后处理顶点和片段着色器

时间:2017-12-29 13:04:31

标签: unity3d fragment shader vertex

经过几个小时的谷歌,复制粘贴代码和游戏,我仍然无法找到解决问题的方法。

我尝试使用顶点和片段函数编写后处理着色器。我的问题是我不知道如何计算当前顶点到世界坐标中摄像机位置(或任何其他给定位置)的径向距离。

我的目标如下:

考虑一个非常大的3D平面,其中摄像头位于顶部,并且看起来完全俯视平面。我现在想要一个后处理着色器,它在平面上绘制一条白线,这样只有那些与相机有一定径向距离的像素才会被涂成白色。预期结果将是一个白色圆圈(在此特定设置中)。

我知道如何在原则上做到这一点,但问题是我无法找到如何计算到顶点的径向距离。

这里的问题可能是这是一个POSTPROCESSING着色器。因此,此着色器不适用于某个对象。如果我这样做,我可以使用mul(unity_ObjectToWorld, v.vertex)获取顶点的世界坐标,但是对于后处理着色器,这会给出一个无意义的值。

这是我对此问题的调试代码:

Shader "NonHidden/TestShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent-1"}
        LOD 100

        ZWrite Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            sampler2D _CameraDepthTexture;
            uniform float4 _MainTex_TexelSize;

            // V2F
            struct v2f {
                float4 outpos  : SV_POSITION;
                float4 worldPos : TEXCOORD0;
                float3 rayDir : TEXCOORD1;
                float3 camNormal : TEXCOORD2;
            };


            // Sample Depth
            float sampleDepth(float2 uv) {
                return Linear01Depth(
                        UNITY_SAMPLE_DEPTH(
                            tex2D(_CameraDepthTexture, uv)));
            }


            // VERTEX
            v2f vert (appdata_tan v)
            {
                TANGENT_SPACE_ROTATION;

                v2f o;
                o.outpos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.rayDir = mul(rotation, ObjSpaceViewDir(v.vertex));
                o.camNormal = UNITY_MATRIX_IT_MV[2].xyz;
                return o;
            }

            // FRAGMENT
            fixed4 frag (v2f IN) : SV_Target
            {
                // Get uv coordinates
                float2 uv = IN.outpos.xy * (_ScreenParams.zw - 1.0f);

                // Flip y if necessary
                #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                {
                    uv.y = 1 - uv.y;
                }
                #endif

                // Get depth
                float depth = sampleDepth(uv);

                // Set color
                fixed4 color = 0;
                if(depth.x < 1)
                {
                    color.r = IN.worldPos.x;
                    color.g = IN.worldPos.y;
                    color.b = IN.worldPos.z;
                }

                return color;
            }       
            ENDCG
        }
    }
}

CurrentState

此图显示相机在飞机上向下看时的结果: Image 1: Actual result 每个像素中的蓝色值(无论出于何种原因)为25。红色和绿色区域反映屏幕的x-y坐标。

即使我稍微旋转相机,我在相同的屏幕坐标处得到完全相同的阴影: Image 2: Actual result with rotated camera

这告诉我计算出来的&#34; worldPos&#34;坐标是屏幕坐标,与飞机的世界坐标无关。

预期结果

我期望看到的结果如下:

Image 3: Expected result

此处,与相机具有相同(径向)距离的像素具有相同的颜色。

如何更改上述代码才能达到此效果?使用rayDir(在vert函数中计算),我试图至少获得从摄像机中心到当前像素的方向向量,这样我就可以使用深度信息计算径向距离。但是rayDir对所有像素都有一个常量值......

此时我还要说我不太了解vert函数内部计算的内容。这只是我在网上找到的东西,我试过了。

1 个答案:

答案 0 :(得分:1)

好吧,我找到了解决问题的方法,因为我在这里发现了这个视频:Shaders Case Study - No Man's Sky: Topographic Scanner

在视频描述中是指向相应GIT存储库的链接。我下载,分析并重写了代码,使其符合我的目的,更易于阅读和理解。

我学到的主要内容是,没有内置的方法来使用后处理着色器计算径向距离(如果我错了,请纠正我!)。因此,为了获得径向距离,实际上唯一的方法是使用从摄像机到顶点和深度缓冲区的方向矢量。由于方向向量也无法以内置方式提供,因此使用了一种技巧:

可以使用自定义Blit函数来设置一些其他着色器变量,而不是在后处理脚本中使用Graphics.Blit函数。在这种情况下,摄像机的平截头体存储在第二组纹理坐标中,然后在着色器代码中可用作TEXCOORD1。这里的技巧是相应的着色器变量自动包含一个插值的uv值,它与我正在寻找的方向向量(&#34;视锥光线&#34;)相同。

调用脚本的代码现在看起来如下:

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class TestShaderEffect : MonoBehaviour
{

    private Material material;
    private Camera cam;

    void OnEnable()
    {
        // Create a material that uses the desired shader
        material = new Material(Shader.Find("Test/RadialDistance"));

        // Get the camera object (this script must be assigned to a camera)
        cam = GetComponent<Camera>();

        // Enable depth buffer generation#
        // (writes to the '_CameraDepthTexture' variable in the shader)
        cam.depthTextureMode = DepthTextureMode.Depth;
    }

    [ImageEffectOpaque] // Draw after opaque, but before transparent geometry
    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        // Call custom Blit function
        // (usually Graphics.Blit is used)
        RaycastCornerBlit(source, destination, material);
    }


    void RaycastCornerBlit(RenderTexture source, RenderTexture destination, Material mat)
    {

        // Compute (half) camera frustum size (at distance 1.0)
        float angleFOVHalf = cam.fieldOfView / 2 * Mathf.Deg2Rad;
        float heightHalf = Mathf.Tan(angleFOVHalf);
        float widthHalf = heightHalf * cam.aspect;      // aspect = width/height

        // Compute helper vectors (camera orientation weighted with frustum size)
        Vector3 vRight = cam.transform.right * widthHalf;
        Vector3 vUp = cam.transform.up * heightHalf;
        Vector3 vFwd = cam.transform.forward;


        // Custom Blit
        // ===========

        // Set the given destination texture as the active render texture
        RenderTexture.active = destination;

        // Set the '_MainTex' variable to the texture given by 'source'
        mat.SetTexture("_MainTex", source);

        // Store current transformation matrix
        GL.PushMatrix();    

        // Load orthographic transformation matrix
        // (sets viewing frustum from [0,0,-1] to [1,1,100])
        GL.LoadOrtho();     

        // Use the first pass of the shader for rendering
        mat.SetPass(0);

        // Activate quad draw mode and draw a quad
        GL.Begin(GL.QUADS);
        {

            // Using MultiTexCoord2 (TEXCOORD0) and Vertex3 (POSITION) to draw on the whole screen
            // Using MultiTexCoord to write the frustum information into TEXCOORD1
            // -> When the shader is called, the TEXCOORD1 value is automatically an interpolated value

            // Bottom Left
            GL.MultiTexCoord2(0, 0, 0);
            GL.MultiTexCoord(1, (vFwd - vRight - vUp) * cam.farClipPlane);
            GL.Vertex3(0, 0, 0);

            // Bottom Right
            GL.MultiTexCoord2(0, 1, 0);
            GL.MultiTexCoord(1, (vFwd + vRight - vUp) * cam.farClipPlane);
            GL.Vertex3(1, 0, 0);

            // Top Right
            GL.MultiTexCoord2(0, 1, 1);
            GL.MultiTexCoord(1, (vFwd + vRight + vUp) * cam.farClipPlane);
            GL.Vertex3(1, 1, 0);

            // Top Left
            GL.MultiTexCoord2(0, 0, 1);
            GL.MultiTexCoord(1, (vFwd - vRight + vUp) * cam.farClipPlane);
            GL.Vertex3(0, 1, 0);

        }
        GL.End();   // Finish quad drawing

        // Restore original transformation matrix
        GL.PopMatrix();
    }
}

着色器代码如下所示:

Shader "Test/RadialDistance"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct VertIn
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 ray : TEXCOORD1;
            };

            struct VertOut
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 interpolatedRay : TEXCOORD1;
            };


            // Parameter variables
            sampler2D _MainTex;

            // Auto filled variables
            float4 _MainTex_TexelSize;
            sampler2D _CameraDepthTexture;


            // Generate jet-color-sheme color based on a value t in [0, 1]
            half3 JetColor(half t)
            {
                half3 color = 0;
                color.r = min(1, max(0, 4 * t - 2));
                color.g = min(1, max(0, -abs( 4 * t - 2) + 2));
                color.b = min(1, max(0, -4 * t + 2));
                return color;
            }


            // VERT
            VertOut vert(VertIn v)
            {
                VertOut o;

                // Get vertex and uv coordinates
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv.xy;

                // Flip uv's if necessary
                #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                    o.uv.y = 1 - o.uv.y;
                #endif              

                // Get the interpolated frustum ray
                // (generated the calling script custom Blit function)
                o.interpolatedRay = v.ray;

                return o;
            }


            // FRAG
            float4 frag (VertOut i) : SV_Target
            {
                // Get the color from the texture
                half4 colTex = tex2D(_MainTex, i.uv);

                // flat depth value with high precision nearby and bad precision far away???
                float rawDepth = DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv));

                // flat depth but with higher precision far away and lower precision nearby???
                float linearDepth = Linear01Depth(rawDepth);

                // Vector from camera position to the vertex in world space
                float4 wsDir = linearDepth * i.interpolatedRay;

                // Position of the vertex in world space
                float3 wsPos = _WorldSpaceCameraPos + wsDir;

                // Distance to a given point in world space coordinates
                // (in this case the camera position, so: dist = length(wsDir))
                float dist = distance(wsPos, _WorldSpaceCameraPos);

                // Get color by distance (same distance means same color)
                half4 color = 1;
                half t = saturate(dist/100.0);
                color.rgb = JetColor(t);

                // Set color to red at a hard-coded distance -> red circle
                if (dist < 50 && dist > 50 - 1 && linearDepth < 1)
                {
                    color.rgb = half3(1, 0, 0);
                }

                return color * colTex;
            }
            ENDCG
        }
    }
}

我现在能够达到预期的效果:

Image

但是我仍然有一些问题,如果有人能为我解答,我会感激不尽:

  • 是否真的没有办法获得径向距离?使用direciton向量和深度缓冲区是低效且不准确的
  • 我不太了解rawDepth变量的内容。我的意思是,它是一些深度信息,但如果你使用深度信息作为纹理颜色,如果你没有可笑地靠近一个物体,你基本上会得到一个黑色图像。这导致对于更远的物体的分辨率非常差。任何人都可以使用它?
  • 我不明白Linear01Depth函数究竟是做什么的。由于Unity文档总体上很糟糕,因此它也没有提供有关此文档的任何信息