Superficie de onda sinusoidal del mapa de desplazamiento unitario

El objetivo de este artículo es hacer una superficie de onda sinusoidal que fluctúe en el tiempo, el efecto es el siguiente

 Primero crea un plano en unidad, no hay nada que decir.

Mosaico

El número predeterminado de caras planas en la unidad es muy pequeño, no lo suficiente para formar una superficie ondulada suave, por lo que el primer paso es subdividir la superficie.

 Como se muestra en la figura, primero debemos ingresar los datos del vértice original, que se escribirán en forma de sombreador de vértices en la unidad, pero el sombreador de vértices real está detrás.

En el programa de casco y el programa de dominio, podemos establecer las reglas de subdivisión. El principio específico de la subdivisión de la superficie no se describirá aquí, solo sepa cómo usarlo. Cabe señalar que los parámetros en hullfun determinan el grado de subdivisión, aquí le asignamos directamente un valor, una forma más científica puede ser establecer su valor de acuerdo a la distancia de la lente.

TessellationFactors hullFun (InputPatch<tessVertexData,3> v) {
	TessellationFactors o;
	o.edge[0] = _TessellationUniform;//设定的参数
	o.edge[1] = _TessellationUniform;
	o.edge[2] = _TessellationUniform;
	o.inside = _TessellationUniform;
	return o;
}

[UNITY_domain("tri")] 表示适用于三角形,还有quad(四边形)
[UNITY_outputcontrolpoints(3)]//输出的控制点数量
[UNITY_outputtopology("triangle_cw")]//输出拓扑结构为顺时针三角形,还有triangle_ccw(逆时针三角形)、line(线段)
[UNITY_partitioning("fractional_odd")]//分数分割模式,还有integer(整数模式)
[UNITY_patchconstantfunc("hullFun")]//细分函数
            //hull着色器:定义细分规则
tessVertexData hul (InputPatch<tessVertexData,3> v, uint id : SV_OutputControlPointID) {
			    return v[id];
}

[UNITY_domain("tri")]
//domain着色器:计算细分后的顶点位置和数据,同时执行顶点着色器
v2g dom (TessellationFactors tessFactors, const OutputPatch<tessVertexData,3> vi, float3 bary : SV_DomainLocation) {
			    vertexData v;
			    v.vertex = vi[0].vertex*bary.x + vi[1].vertex*bary.y + vi[2].vertex*bary.z;
			    v.tangent = vi[0].tangent*bary.x + vi[1].tangent*bary.y + vi[2].tangent*bary.z;
			    v.normal = vi[0].normal*bary.x + vi[1].normal*bary.y + vi[2].normal*bary.z;
			    v.uv = vi[0].uv*bary.x + vi[1].uv*bary.y + vi[2].uv*bary.z;
                return vert (v);
}

Para ver la cuadrícula claramente, representamos la cuadrícula en forma de líneas. El principio de representación es usar el sombreador de geometría para calcular el centro de gravedad de cada malla triangular e interpolar el color de acuerdo con la distancia mínima desde el Coordenadas del centro de gravedad del triángulo a los tres vértices. .

            [maxvertexcount(3)]
            //几何着色器
            void geo (
	            triangle v2g v[3],
	            inout TriangleStream<g2f> tStream
            ) {
                float4 barycenter = (v[0].vertex + v[1].vertex + v[2].vertex)/3;
                float3 normal = (v[0].normal + v[1].normal + v[2].normal)/3;

	            v[0].normal = normal;
	            v[1].normal = normal;
	            v[2].normal = normal;

	            g2f g0, g1, g2;
	            g0.data = v[0];
	            g1.data = v[1];
	            g2.data = v[2];

                //
	            g0.barycentricCoordinates = float3(0, 0, 1);
	            g1.barycentricCoordinates = float3(0, 1, 0);
	            g2.barycentricCoordinates = float3(1, 0, 0);

	            tStream.Append(g0);
	            tStream.Append(g1);
	            tStream.Append(g2);
                tStream.RestartStrip();
            }


			fixed4 frag (g2f i) : SV_Target
			{
                fixed4 col = tex2D(_MainTex, i.data.uv);
                float3 barys = i.barycentricCoordinates;
	            float3 deltas = fwidth(barys);
	            float3 smoothing = deltas * _WireframeSmoothing;
	            float3 thickness = deltas * _WireframeThickness;
	            barys = smoothstep(thickness, thickness + smoothing, barys);
	            float minBary = min(barys.x, min(barys.y, barys.z));

	            return float4(lerp(_WireframeColor, col, minBary),1);//
				return col;
			}
			ENDCG
		} 

La parte anterior se refiere al artículo de lyh cute master of station b https://www.bilibili.com/read/cv16290237

sombreador de cálculo

Como dice el título, vamos a utilizar un mapa de desplazamiento variable en el tiempo, que obviamente debe generarse en tiempo real. Puede elegir crear una nueva textura cada vez en la función de actualización del script y luego llenarla una por una. Si hace esto, encontrará que la velocidad de fotogramas es muy baja, especialmente si el rendimiento de la CPU no es bueno, porque la CPU no es buena para hacer este tipo de cálculo paralelo. Así que le entregamos esta tarea a la GPU, que usa el sombreador de cómputo.

Aunque todos son sombreadores, el sombreador de cómputo en Unity está escrito en lenguaje HLSL de estilo DirectX 11, por lo que está escrito de manera diferente a otros sombreadores y no está en la canalización de representación normal.

La siguiente figura es el sombreador de cálculo predeterminado, que contiene las partes más importantes.inserte la descripción de la imagen aquí

La oración #pragma kernel CSMain puede entenderse como la declaración de una determinada función en CS (el nombre de la función es CSMain).

RWTexture2D<float4> Resultado: RW en realidad significa lectura y escritura, y Texture2D es una textura bidimensional, por lo que significa una textura bidimensional que Compute Shader puede leer y escribir. CS es un programa que se ejecuta en la GPU y es independiente de la canalización de renderizado, por lo que se requiere un operador para realizar la entrada o salida. Para fines de renderizado, generalmente usamos una textura como soporte.

En general, nuestro sombreador suele ser de solo lectura, y la mayoría de ellos usan sampler2D y luego acceden a las coordenadas UV a través de la función tex2D, pero se accede directamente al acceso de RWTexture2D a través de Result[uint2(0,0)], y el valor es de tipo float4.

Dado que esta textura debe leerse y escribirse, debe usar una textura de renderizado en lugar de Texture2D. Su método de creación es el siguiente, preste atención a la necesidad de habilitar su lectura y escritura, y llame al método de creación.

public RenderTexture Displace;
...
    void Start()
    {
        Displace = new RenderTexture(1024, 1024,0, RenderTextureFormat.ARGBFloat);
        Displace.enableRandomWrite = true;
        Displace.Create();
        ...
    }

 [numthreads(8,8,1)] indica la cantidad de subprocesos en un grupo de subprocesos, es decir, 8 * 8 * 1. La configuración del grupo de subprocesos afectará la eficiencia del cálculo, pero no conozco los detalles. Si alguien sabe, espero que me pueda dar algún consejo. . Aquí lo dejamos por defecto.

Finalmente, la función kernel, lo importante es que puede tener varios parámetros de entrada

SV_GroupID : la identificación del grupo de subprocesos
SV_GroupIndex : en cada elemento del grupo de subprocesos, el índice del subproceso, [numthreads(8,8,1)], el rango de índice (0, 0, 0) - (8, 8, 0),
SV_DispatchThreadID : esta es la identificación global única, que puede entenderse como las coordenadas de cada píxel de una imagen

Entonces, id.x id.y en la figura anterior representan respectivamente las coordenadas horizontal y vertical de un píxel en la textura.

Después de escribir un CS, el siguiente paso es cómo usarlo.

public class createTexture : MonoBehaviour
{
    public ComputeShader cshader;
    private int kernelHandle;

    ...

    void Start()
    {
        ...
        kernelHandle = cshader.FindKernel("CSMain");
    }

    void Update()
    {
        cshader.SetTexture(kernelHandle, "Result", Displace);
        cshader.Dispatch(kernelHandle, 1024 / 8, 1024 / 8, 1);
        ...
    }
}

Necesitamos definir un sombreador de cómputo y definir un índice (tipo int) para su función de kernel

Pase una textura de renderizado como RWTexture2D.

Finalmente, llame al método dispatch para iniciar la operación de acuerdo con el tamaño de la textura (1024*1024) que le pasamos

Esta parte se refiere a la publicación del blog.

El siguiente es el CS real utilizado. Guardo el desplazamiento en el canal R de la textura. Tenga en cuenta que el PI constante se define mejor con una macro

#pragma kernel CSMain
#define PI 3.14159274f

RWTexture2D<float4> Result;

float time;
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    Result[id.xy] = float4((cos(id.x/ 1024.0f * 2 * PI*5+time) + cos(id.y/1024.0f * 2 * PI*5+time)) * 0.25f + 0.5f, 0, 0, 1);
}

mapa de desplazamiento

El paso final es aplicar el mapa de desplazamiento al renderizado.

Pase la textura calculada por el sombreador de cálculo al sombreador, asígnele el nombre _DisplaceTex, lea su canal R, calcule el desplazamiento y agréguelo a la dirección normal.

Tenga en cuenta que tex2Dlod() debe usarse aquí para el muestreo de texturas, porque tex2D no se puede usar en sombreadores de vértices (por razones desconocidas).

		v2g vert(vertexData v)
		{
			...
			float d = pow( tex2Dlod(_DisplaceTex, float4(v.uv.xy, 0, 0)).r, _Power) * _Displacement;
			v.vertex.xyz += v.normal * d;
            ...
		}

Supongo que te gusta

Origin blog.csdn.net/qq_43533956/article/details/127408471
Recomendado
Clasificación