【问题标题】:How to compute the radial distance of an object in a postprocessing vertex and fragment shader如何在后处理顶点和片段着色器中计算对象的径向距离
【发布时间】:2019-09-05 00:14:07
【问题描述】:

经过数小时的谷歌、复制粘贴代码和玩耍,我仍然找不到解决问题的方法。

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

我的目标如下:

考虑一个非常大的 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
        }
    }
}

当前状态

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

即使我稍微旋转相机,我也会在相同的屏幕坐标处获得完全相同的阴影:

这表明计算出的“worldPos”坐标是屏幕坐标,与平面的世界坐标无关。

预期结果

我希望看到的结果如下:

在这里,与相机具有相同(径向)距离的像素具有相同的颜色。

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

在这一点上,我还不得不说,我并不真正理解 vert 函数内部计算的内容。这只是我在互联网上找到并尝试过的东西。

【问题讨论】:

  • 请阅读如何创建minimal, complete, verifiable example 并通过编辑将缺少的信息添加到您的帖子中:) 如果您还没有阅读[如何提问](stackoverflow.com/help/how-to-ask) 但我建议这样做:) 我强烈建议遵循我链接的 2 个指南,因为当帖子遵循这些指南时,SO 上的人更有可能回答问题。我特别缺少的是 - 到目前为止你尝试了什么?您面临哪些错误/问题?您可能有有助于理解您的问题的 coden-ps 吗?欢迎使用 StackOverflow
  • @Tobias:好建议。其中一个链接在上面有点损坏 - 您可能想知道您可以在 cmets 中使用 [ask][mcve],它会自动扩展到这些链接。
  • @halfer 是的,我知道链接已损坏 - 但我无法再编辑此评论。而且我总是以复杂的方式将它们联系起来。这是我多年来得到的最好建议!我敢打赌我可以在任何地方找到这些“快捷方式”的列表:D
  • @Tobias:我想我已经在帮助中心看到了它们的文档,但你真的必须搜索它;-)

标签: unity3d shader vertex


【解决方案1】:

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

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

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

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

调用脚本的代码现在如下所示:

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
        }
    }
}

我现在可以达到预期的效果了:

但我仍然有一些问题,如果有人可以为我解答,我将不胜感激:

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

【讨论】:

  • 你能不能用紫外线做点什么?也许类似于获取 UV 制作的矢量的长度,然后重新映射它? Idk 我不是着色器专家哈哈 :)
猜你喜欢
  • 2019-07-31
  • 1970-01-01
  • 2019-01-24
  • 1970-01-01
  • 2015-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-14
相关资源
最近更新 更多