Нарисуйте текстуру объекта, основываясь на позиции попадания лучей в мировом пространстве. - PullRequest
2 голосов
/ 05 марта 2020

Я пытаюсь получить мировое положение пикселя внутри фрагментированного шейдера.

Позвольте мне объяснить. Я следовал учебнику для фрагментированного шейдера, который позволяет рисовать на объектах. Прямо сейчас это работает через текстурные координаты, но я хочу, чтобы он работал через мировое положение пикселя. Поэтому, когда я нажимаю на 3D-модель, чтобы иметь возможность сравнить положение Vector3 (где произошел щелчок) с положением пикселя Vector3, и если расстояние достаточно мало, чтобы отобразить цвет.

Это настройка I иметь. Я создал новый 3D-проект только для создания шейдера с намерением экспортировать его позже в мой основной проект. В сцене у меня есть основная камера по умолчанию, направленный свет, объект со скриптом, который показывает мне fps, и 3D куб по умолчанию с коллайдером me sh. Я создал новый материал и новый стандартный шейдер поверхности и добавил его в куб. После этого я назначил следующий сценарий C# кубу с шейдером и ссылкой на камеру.

Обновление: Сейчас проблема в том, что блиц работает не так, как ожидалось. Если вы измените сценарий шейдера так, как сказал Калле, удалите блит из сценария c# и измените шейдер из материала 3D-модели на шейдер Draw, он будет работать, как и ожидалось, но без какого-либо освещения. Для моих целей мне пришлось изменить расстояние (_Mouse. xyz , i.worldPos. xyz ); на расстояние (_Mouse. xz , i.worldPos. xz ); , поэтому он будет рисовать всю дорогу с другой стороны. Для отладки я создал RenderTexture, и каждый кадр я использую Blit, чтобы обновить текстуру и посмотреть, что происходит. Текстура рендеринга не удерживает правильную позицию, поскольку объект окрашен. У моей трехмерной модели много геометрии, и когда краска переходит на другую сторону, она должна быть повсюду на текстуре рендеринга ... но сейчас она находится на одной линии от верха до низа текстуры. Также я пытаюсь нарисовать нижнюю половину объекта, и текстура рендера ничего не показывает. Только когда я рисую в верхней половине, я вижу красные линии (цвет по умолчанию).

Если вы хотите, вы можете загрузить пример проекта здесь .

This это код, который я использую.

Draw.shader

Shader "Unlit/Draw"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Coordinate("Coordinate",Vector)=(0,0,0,0)
        _Color("Paint 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;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Coordinate,_Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                float draw =pow(saturate(1-distance(i.uv,_Coordinate.xy)),100);
                fixed4 drawcol = _Color * (draw * 1);
                return saturate(col + drawcol);
            }
            ENDCG
        }
    }
}

Draw.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Draw : MonoBehaviour
{
    public Camera cam;
    public Shader paintShader;

    RenderTexture splatMap;
    Material snowMaterial,drawMaterial;

    RaycastHit hit;

    private void Awake()
    {
        Application.targetFrameRate = 200;
    }
    void Start()
    {
        drawMaterial = new Material(paintShader);
        drawMaterial.SetVector("_Color", Color.red);

        snowMaterial = GetComponent<MeshRenderer>().material;
        splatMap = new RenderTexture(1024, 1024, 0, RenderTextureFormat.ARGBFloat);
        snowMaterial.mainTexture = splatMap;
    }
    void Update()
    {
        if (Input.GetMouseButton(0))
        {
            if(Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition),out hit))
            {
                drawMaterial.SetVector("_Coordinate", new Vector4(hit.textureCoord.x, hit.textureCoord.y, 0, 0));
                RenderTexture temp = RenderTexture.GetTemporary(splatMap.width, splatMap.height, 0, RenderTextureFormat.ARGBFloat);
                Graphics.Blit(splatMap, temp);
                Graphics.Blit(temp, splatMap, drawMaterial);
                RenderTexture.ReleaseTemporary(temp);
            }
        }
    }
}

Что касается того, что я пытался решить, проблема заключается в следующем. Я искал в Google информацию о проблеме, о которой идет речь, и пытался реализовать ее в своем проекте. Я также нашел несколько проектов, в которых есть нужная мне функция, например, Me sh Texuture Painting . Этот работает именно так, как мне нужно, но он не работает на iOS. 3D объект становится черным. Вы можете проверить предыдущий пост , который я сделал, чтобы решить проблему, а также поговорил с создателем в твиттере, но он не может помочь мне. Также я попробовал этот актив , который работает нормально, но в моем основном проекте выполняется с очень небольшим количеством кадров в секунду, мне сложно настроить его для своих нужд, и он не рисует по краям моей 3D-модели.

Шейдер, который хорошо работает, достаточно прост, поэтому я могу изменить его и получить желаемый эффект, как показано выше.

Спасибо!

1 Ответ

1 голос
/ 06 марта 2020

Существует два подхода к этой проблеме - либо вы передаете координату текстуры и пытаетесь преобразовать ее в мировое пространство внутри шейдера, либо вы переходите в мировую позицию и сравниваете ее с мировой позицией фрагмента. Последнее, без сомнения, самое простое.

Итак, допустим, что вы передаете мировую позицию в шейдер следующим образом:

drawMaterial.SetVector("_Coordinate", new Vector4(hit.point.x, hit.point.y, hit.point.z, 0));

Расчет мировой позиции для фрагмента стоит дорого, поэтому мы делаем это внутри вершинного шейдера и позвольте аппаратному обеспечению интерполировать значение на фрагмент. Давайте добавим мировую позицию в нашу структуру v2f:

struct v2f
{
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;
    float3 worldPos : TEXCOORD1;
};

Чтобы вычислить мировую позицию внутри вершинного шейдера, мы можем использовать встроенную матрицу unity_ObjectToWorld:

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    return o;
 }

Наконец, мы можем получить доступ к значению в фрагментном шейдере следующим образом:

float draw =pow(saturate(1-distance(i.worldPos,_Coordinate.xyz)),100);

РЕДАКТИРОВАТЬ: Я только что понял - когда вы делаете проход блит, вы не рендеринг со своим sh, вы рендеринг в квад, который покрывает весь экран. Из-за этого, когда вы вычисляете расстояние до вершины, вы получаете расстояние до углов экрана, что не правильно. Хотя есть способ решить эту проблему - вы можете изменить цель рендеринга на вашу текстуру рендеринга и нарисовать me sh, используя шейдер, который проецирует me sh UV на экран.

Это немного Это сложно объяснить, но в основном способ работы вершинных шейдеров заключается в том, что вы берете вершину, которая находится в локальном объектном пространстве, и трансформируете ее так, чтобы она была относительно экрана в пространстве от -1 до 1 по обеим осям, где 0 находится в центр. Это называется нормализованным пространством координат устройства или ND C. Мы можем использовать это, чтобы сделать так, чтобы вместо использования матриц модели и камеры для преобразования наших вершин мы использовали координаты UV, преобразованные из пространства [0,1] в [-1,1]. В то же время мы можем рассчитать нашу мировую позицию и передать ее на фрагмент отдельно. Вот как будет выглядеть шейдер:

v2f vert (appdata v)
{
    v2f o;

    float2 uv = v.texcoord.xy;

    // https://docs.unity3d.com/Manual/SL-PlatformDifferences.html

    if (_ProjectionParams.x < 0) {
        uv.y = 1 - uv.y;
    }

    // Convert from 0,1 to -1,1, for the blit
    o.vertex = float4(2 * (uv - 0.5), 0, 1);

    // We still need UVs to draw the base texture
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);

    // Let's do the calculations in local space instead!
    o.localPos = v.vertex.xyz;

    return o;
 }

Также не забудьте передать переменную _Coordinate в локальном пространстве, используя transform.InverseTransformPoint.

Теперь нам нужно использовать другой подход для рендеринга в текстуру рендеринга. По сути, нам нужно визуализировать фактическое me sh, как если бы мы выполняли рендеринг с камеры - за исключением того, что это me sh будет отображаться в виде развернутого УФ-листа на экране. Сначала мы устанавливаем активную текстуру рендеринга для текстуры, в которую мы хотим нарисовать:

// Cache the old target so that we can reset it later
RenderTexture previousRT = RenderTexture.active;
RenderTexture.active = temp;

(Вы можете прочитать о том, как работают цели рендеринга здесь ) Далее, нам нужно связать нашу материал и нарисуйте меня sh.

Material mat = drawMaterial;
Mesh mesh = yourAwesomeMesh;
mat.SetTexture("_MainTex", splatMap);
mat.SetPass(0); // This tells the renderer to use pass 0 from this material
Graphics.DrawMeshNow(mesh, Vector3.zero, Quaternion.identity);

Наконец, верните текстуру обратно к оригиналу:

// Remember to reset the render target
RenderTexture.active = previousRT;
Graphics.Blit(temp, splatMap);

Я не проверял и не проверял это, но я использовал похожую технику, чтобы нарисовать меня sh в УФ раньше. Вы можете прочитать больше о DrawMeshNow здесь .

...