Как применить текстуры к материалу на основе видового экрана камеры в Unity - PullRequest
1 голос
/ 25 сентября 2019

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

По сути, мне нужно загружать изображения более высокого качества, примененные к материалу на земном шаре, когда пользователь увеличивает масштаб камеры.В настоящее время я загружаю все изображения одновременно в зависимости от уровня масштабирования, который совершенно не оптимизирован при более низких уровнях масштабирования и невозможен при более высоких, так как массив texture2darray может содержать только 2048 изображений.

Что мне нужно знать, так это как я могу загружать только те изображения, которые просматривает камера, и не беспокоиться о них за кадром?

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

Вот мой текущий класс листов:

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

public class TilerHelper : MonoBehaviour
{
    MapTiler tiler = new MapTiler("G:/s2cloudless_4326_v1.0.0.sqlite"); //Function that loads from the sql table
    public Material mat; //The material
    Texture2DArray texArr; //The array that will be made into the final texture
    public Texture2D[] textures; //The images loaded from the sql table
    int size = 2048; //A texture2darray can only hold 2048 textures
    int count = 0; //Iterator for loading images
    int zoom = 1; //Determines which zoom level to load from the sql table
    int lastZoom = 1; //Determines whether the zoom level has moved
    CameraOrbit cam; //The main camera
    int initX = 3; //The x value of the image at zoom level 1
    int initY = 1;//The y value of the image at zoom level 1

    // Start is called before the first frame update
    void Start()
    {
        SetTiles(3, 1, 1, 4, 2); //This loads the first set of images at max distance when the program starts
        cam = Camera.main.gameObject.GetComponent<CameraOrbit>();
    }

    // Update is called once per frame
    void Update()
    {
        //These different zoom levels are based on the camera distance from the earth (large earth, so large numbers)
        if (cam.distance <= 90000) zoom = 12;
        else if (cam.distance <= 100000) zoom = 11;
        else if (cam.distance <= 110000) zoom = 10;
        else if (cam.distance <= 130000) zoom = 9;
        else if (cam.distance <= 150000) zoom = 8;
        else if (cam.distance <= 170000) zoom = 7;
        else if (cam.distance <= 190000) zoom = 6;
        else if (cam.distance <= 210000) zoom = 5;
        else if (cam.distance <= 230000) zoom = 4;
        else if (cam.distance <= 250000) zoom = 3;
        else if (cam.distance <= 270000) zoom = 2;
        else if (cam.distance >= 270000) zoom = 1;

        //If the camera has gone to a new zoom level...
        if(lastZoom != zoom)
        {
            if (zoom == 1) SetTiles(initX, initY, zoom, 4, 2); //Set it to 1 manually, since the formula won't work for it
            else
            {
                //This formula will load the correct images in the correct places regardless of zoom level
                int counter = 1;
                int resultX = initX;
                int resultY = initY;
                while(counter < zoom)
                {
                    resultX = resultX * 2 + 1;
                    resultY = resultY * 2 + 1;
                    counter++;
                }
                SetTiles(resultX, resultY, zoom, (int)Mathf.Pow(2, zoom + 1), (int)Mathf.Pow(2, zoom));
            }

            lastZoom = zoom;  //Update last zoom
        }
    }
    //The method that actually places the images
    void SetTiles(int x, int y, int z, int columns, int rows)
    {
        textures = new Texture2D[size]; //The array to hold all the textures
        //Load and place all the images according to passed x, y, and zoom level
        for (int i = 0; i <= x; i++)
        {
            for (int j = 0; j <= y; j++)
            {
                textures[count] = tiler.Read(i, j, z); //The z determines the zoom level, so I wouldn't want them all loaded at once
                count++;     
            }
        }
        count = 0; //Reset the counter

        //Instantiate the texture2darray
        texArr = new Texture2DArray(256, 256, textures.Length, TextureFormat.RGBA32, false, true);
        texArr.filterMode = FilterMode.Bilinear;
        texArr.wrapMode = TextureWrapMode.Clamp;
        //Set the texture2darray to contain all images loaded
        for (int i = 0; i < textures.Length; i++)
        {
            if (textures[i] == null) continue;
            texArr.SetPixels(textures[i].GetPixels(), i, 0);
        }
        //Apply the texture and set appropriate material values
        texArr.Apply();
        mat.SetTexture("_MainTexArray", texArr);
        Matrix4x4 matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, -90, 0), Vector3.one);
        mat.SetMatrix("_Matrix", matrix);
        mat.SetInt("_COLUMNS", columns);
        mat.SetInt("_ROWS", rows);
    }
}

А вот и мой код шейдера

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Tiling"
{
    Properties
    {
        _MainTexArray("Tex", 2DArray) = "" {}
        _COLUMNS("Columns", Int) = 8
        _ROWS("Rows", Int) = 4
    }
        SubShader
        {
            Pass{
            Tags {"RenderType" = "Opaque"}
            Lighting Off
            ZWrite Off

                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma require 2darray

                #include "UnityCG.cginc"

                struct appdata 
                {
                   float4 vertex : POSITION;
                   float3 normal : NORMAL;
                };
                struct v2f
                {
                    float4 pos : SV_POSITION;
                    float3 normal : TEXCOORD0;
                };

                float4x4 _Matrix;
                v2f vert(appdata v, float3 normal : TEXCOORD0)
                {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);
                    o.normal = mul(v.normal, _Matrix);
                    return o;
                }

                UNITY_DECLARE_TEX2DARRAY(_MainTexArray);

                int _ROWS;
                int _COLUMNS;

                #define PI 3.141592653589793

                inline float2 RadialCoords(float3 a_coords)
                {
                    float3 a_coords_n = normalize(a_coords);
                    float lon = atan2(a_coords_n.z, a_coords_n.x);
                    float lat = acos(a_coords_n.y);
                    float2 sphereCoords = float2(lon, lat) * (1.0 / PI);
                    return float2(sphereCoords.x * 0.5 + 0.5, 1 - sphereCoords.y);
                }

                float _UVClamp;

                float4 frag(v2f IN) : COLOR
                {
                    float2 equiUV = RadialCoords(IN.normal);

                    float2 texIndex;
                    float2 uvInTex = modf(equiUV * float2(_COLUMNS, _ROWS), texIndex);

                    int flatTexIndex = texIndex.x * _ROWS + texIndex.y;

                    return UNITY_SAMPLE_TEX2DARRAY(_MainTexArray,
                    float3(uvInTex, flatTexIndex));
                }
                ENDCG
            }
        }
}

Спасибо.

1 Ответ

1 голос
/ 25 сентября 2019

Интересная проблема, которую нужно попытаться решить, не просматривая множество плиток или пикселей.Вот частичный ответ, который может помочь вам найти решение.

Здесь есть две основные проблемы:

  1. Поиск того, какие «плитки» видны камере.
  2. Загрузка этих плиток в шейдер и правильное их индексирование.

Поиск, какие "плитки" видны камере

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

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

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


Загрузка этих плиток в шейдер и получение ихИндексируйте их правильно.

Это гораздо более простая часть, если вы можете определить, какие прямоугольные листы необходимо отправить.Если вы можете нарисовать прямоугольник, который зацикливается вокруг всех точек, которые может видеть камера, вы можете просто отправить плитки, которые частично или полностью находятся внутри этого «загрузочного прямоугольника».

Вам также понадобитсяотправить начальную и конечную точки прямоугольника.Таким образом, шейдер может рассчитать, как изменить texIndex координаты всей карты на то место, где эта плитка хранится в «прямоугольнике загрузки».

В основном эти линии в шейдере, вероятно, будут одинаковыми:

float2 equiUV = RadialCoords(IN.normal);

float2 texIndex;
float2 uvInTex = modf(equiUV * float2(_COLUMNS, _ROWS), texIndex);

...

return UNITY_SAMPLE_TEX2DARRAY(_MainTexArray,
        float3(uvInTex, flatTexIndex));

Эта строка должна была бы каким-то образом измениться, чтобы также учесть параметры прямоугольника загрузки и то, как он горизонтально оборачивается вокруг карты тайлов:

int flatTexIndex = texIndex.x * _ROWS + texIndex.y;

Извините за неопределенностьособенно для первой части.Удачи.

...