如何创建一个着色器,可以在 Unity 中显示由许多小图像组成的纹理 [英] How to create a shader that can display a texture made out of many small images in Unity

查看:53
本文介绍了如何创建一个着色器,可以在 Unity 中显示由许多小图像组成的纹理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我想要做的是从 SQL 表加载卫星图像并将它们包裹在一个球体周围以创建一个球体.我知道我已经加载了涵盖的图像,我只是不确定如何让我的着色器以正确的方向显示图像.

我去了 Unity 论坛并查看了

这是我用来加载 SQL 图像的代码:

textures = new Texture2D[size];for (int x = 0; x <= 7; x++){for (int y = 0; y <= 3; y++){纹理[计数] = tiler.Read(x, y, 2);//z 决定缩放级别,所以我不希望它们一次全部加载if (textures[count] != null) TextureScale.Bilinear(textures[count], 256, 256);计数++;}}texArr = new Texture2DArray(256, 256, textures.Length, TextureFormat.RGBA32, true, true);texArr.filterMode = FilterMode.Bilinear;texArr.wrapMode = TextureWrapMode.Repeat;for (int i = 0; i 

在 SQL 表中,x 和 y 确定平铺的位置,z 确定缩放级别.我现在只使用一个缩放级别.

很抱歉链接整个着色器类,但我对着色器不是很有经验,所以我不太清楚问题出在哪里.

解决方案

如果您可以索引到照片数组中,从而有效地获得地球的等距柱状投影,您可以尝试使用修改形式的着色器代码

1:

2:

3:

4:

5:

6:

7:

8:

并将行和列设置为 3,它会产生不错的结果:

如果启用了双线性过滤,纹理的边界处仍然存在一些伪影.但是这些文物必须放大得非常近才能看到.由于缺少用于双线性过滤的相邻像素,它们大多表现为混合不当或丢失像素:

当然,这个例子没有正确平铺,所以沿着一条经线有明显的接缝:

由于此设置需要球体的法线,因此这只适用于法线接近球体法线的事物.因此,例如,它不会在平面上正确渲染.

So what I'm trying to do is load satellite images from an SQL table and wrap them around a sphere to create a globe. I know I've got loading the images covered, I'm just not sure how to make my shader display the images in the correct orientation.

I've gone to the Unity Forums as well as checked out this code from the Unity Docs.

Using the linked shader code and the help I received on the forums, here's the code I've ended up with:

Properties
    {
        _MainTexArray("Tex", 2DArray) = "" {}
        _SliceRange("Slices", Range(0,32)) = 6
        _UVScale("UVScale", Float) = 1
        _COLUMNS("Columns", Range(0, 5)) = 1
        _ROWS("Rows", Range(0, 5)) = 1
        _CELLS("Cells", Range(0, 32)) = 16
    }
        SubShader
        {
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                // texture arrays are not available everywhere,
                // only compile shader on platforms where they are
                #pragma require 2darray

                #include "UnityCG.cginc"

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

                float _SliceRange;
                float _UVScale;

                v2f vert(float4 vertex : POSITION)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(vertex);
                    o.uv.xy = (vertex.xy + 0.5) * _UVScale;
                    o.uv.z = (vertex.z + 0.5) * _SliceRange;
                    return o;
                }
                float _COLUMNS; //Columns and rows only go between 0 and 1
                float _ROWS;
                float _CELLS;
                UNITY_DECLARE_TEX2DARRAY(_MainTexArray);        
                half4 frag(v2f i) : SV_Target
                {
                    float3 uv = float3(i.uv.x * _CELLS, i.uv.y * _CELLS, 0);
                    uv.z = floor(i.uv.x / _COLUMNS) * floor(i.uv.y / _ROWS);
                    return UNITY_SAMPLE_TEX2DARRAY(_MainTexArray, uv / _CELLS); 
                }
                ENDCG
            }
        }

Using that I've gotten my materials to look like this:

Here's the code that I'm using to load the SQL images:

textures = new Texture2D[size];
            for (int x = 0; x <= 7; x++) 
            {
                for (int y = 0; y <= 3; y++)
                {
                    textures[count] = tiler.Read(x, y, 2); //The z determines the zoom level, so I wouldn't want them all loaded at once
                    if (textures[count] != null) TextureScale.Bilinear(textures[count], 256, 256);
                    count++;
                }
            }


        texArr = new Texture2DArray(256, 256, textures.Length, TextureFormat.RGBA32, true, true);
        texArr.filterMode = FilterMode.Bilinear;
        texArr.wrapMode = TextureWrapMode.Repeat;
        for (int i = 0; i < textures.Length; i++)
        {
            if (textures[i] == null) continue;
            texArr.SetPixels(textures[i].GetPixels(0), i, 0);
        }    
        texArr.Apply();
        mat.SetTexture("_MainTexArray", texArr);

In the SQL Table, the x and y determines the position of the tile and the z determines the zoom level. I'm just working with one zoom level for now.

Sorry for linking the whole shader class, but I'm not very experienced with shaders so I don't quite know where the problem lies.

解决方案

If you can index into the array of photos such that you effectively have an equirectangular projection of the globe, you could try using a modified form of the shader code by Farfarer from the Unity forums copied and modified slightly below:

Shader "Custom/Equirectangular" {
    Properties{
        _MainTexArray("Tex", 2DArray) = "" {}
        _COLUMNS("Columns", Int) = 2
        _ROWS("Rows", Int) = 2
    }

        SubShader{
            Pass {
                Tags {"LightMode" = "Always"}

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

                    v2f vert(appdata v)
                    {
                        v2f o;
                        o.pos = UnityObjectToClipPos(v.vertex);
                        o.normal = v.normal;
                        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);
                    }

                    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
            }
        }
    FallBack "VertexLit"
}

You also need to use

texArr = new Texture2DArray(256, 256, textures.Length, TextureFormat.RGBA32, false, true);

instead of

texArr = new Texture2DArray(256, 256, textures.Length, TextureFormat.RGBA32, true, false); 

It works for me if I attach this script to a sphere:

Material myMat;
public List<Texture2D> texes;

IEnumerator Start()
{
    yield return null;
    myMat = GetComponent<Renderer>().material;

    Texture2DArray texArr = new Texture2DArray(256, 256, 9, 
            TextureFormat.RGBA32, false, true);
    texArr.filterMode = FilterMode.Bilinear;
    texArr.wrapMode = TextureWrapMode.Clamp;

    for (int i = 0 ; i < texes.Count ; i++)
    {
        texArr.SetPixels(texes[i].GetPixels(), i, 0);
    }

    texArr.Apply();

    myMat.SetTexture("_MainTexArray", texArr);
}

and in texes I add these textures in order:

0:

1:

2:

3:

4:

5:

6:

7:

8:

and set 3 for Rows and Columns, it produces decent results:

If bilinear filtering is enabled, there are still some artifacts at the borders of the textures. But these artifacts have to be zoomed in quite close to see. Due to the lack of adjacent pixels for bilinear filtering they mostly appear as improperly blended or missing pixels:

Of course this example doesn't properly tile so there is an obvious seam along one longitude line:

Since this setup expects normals from a sphere, this only works on things with normals that approximate those of a sphere. So it would not render properly on a plane, for instance.

这篇关于如何创建一个着色器,可以在 Unity 中显示由许多小图像组成的纹理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆