DalerLiss
@DalerLiss
Ребенок в большом теле

Почему не отрисовывается карта нормалей по оси X в триплинарном шейдере на процедурном меше?

Начал изучать как в триплинарном шейдере реализовать карту адекватную нормалей, и наткнулся на статью по этой теме:
https://skine.ru/articles/467963/?ysclid=lpsb39xzk...
https://github.com/bgolus/Normal-Mapping-for-a-Tri...

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

Сверху примитив куба, снизу созданный меш
656f3613d7f8c893212865.png

Код шейдера:
Shader "Custom/Triplanar_Surface_Shader"
{
	Properties
	{
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_TextureColor ("Texture Color", Color) = (1, 1, 1, 1)
		_OffsetAndTiling("Offset And Tiling", Vector) = (0, 0, 0, 1)
		
		[Space]
		[NoScaleOffset] _NormalMap("Normal Map", 2D) = "bump" {}
		_Normal("Normal", Range(-5.0, 5.0)) = 1.0

		[Space]
		[Gamma] _Metallic("Metallic", Range(0, 1)) = 0
		_Glossiness("Smoothness", Range(0, 1)) = 0.5

		[Space]
		[NoScaleOffset] _OcclusionMap("Occlusion", 2D) = "white" {}
		_OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0

		_Test("Test", Range(0, 3)) = 3
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Standard fullforwardshadows
		#pragma target 3.0
		#include "UnityStandardUtils.cginc"

		// flip UVs horizontally to correct for back side projection
		#define TRIPLANAR_CORRECT_PROJECTED_U

		// offset UVs to prevent obvious mirroring
		#define TRIPLANAR_UV_OFFSET

		sampler2D _MainTex;
		float4 _MainTex_ST;
		fixed4 _OffsetAndTiling;

		sampler2D _NormalMap;
		half _Normal;

		half _Glossiness;
		half _Metallic;

		sampler2D _OcclusionMap;
		half _OcclusionStrength;

		int _Test;

		struct Input {
			float3 worldPos;
			float3 worldNormal;
			INTERNAL_DATA
		};

		// Altered to take normals (-1 to 1 ranges) rather than unsigned normal maps (0 to 1 ranges)
		half3 blend_rnm(half3 n1, half3 n2)
		{
			n1.z += 1;
			n2.xy = -n2.xy;

			return n1 * dot(n1, n2) / n1.z - n2;
		}

		float3 WorldToTangentNormalVector(Input IN, float3 normal)
		{
			float3 t2w0 = WorldNormalVector(IN, float3(1,0,0));
			float3 t2w1 = WorldNormalVector(IN, float3(0,1,0));
			float3 t2w2 = WorldNormalVector(IN, float3(0,0,1));
			float3x3 t2w = float3x3(t2w0, t2w1, t2w2);
			return normalize(mul(t2w, normal));
		}

		void surf (Input IN, inout SurfaceOutputStandard o)
		{
			IN.worldPos = (IN.worldPos.xyz * _OffsetAndTiling.w) + _OffsetAndTiling.xyz;
			// work around bug where IN.worldNormal is always (0, 0, 0)!
			IN.worldNormal = WorldNormalVector(IN, float3(0, 0, 1));

			// calculate triplanar blend
			half3 triblend = saturate(pow(IN.worldNormal, 4));
			triblend /= max(dot(triblend, half3(1,1,1)), 0.0001);

			// calculate triplanar uvs applying texture scale and offset values ala TRANSFORM_TEX macro
			float2 uvX = IN.worldPos.zy * _MainTex_ST.xy + _MainTex_ST.zy;
			float2 uvY = -IN.worldPos.xz * _MainTex_ST.xy + _MainTex_ST.zy;
			float2 uvZ = IN.worldPos.xy * _MainTex_ST.xy + _MainTex_ST.zy;

			// offset UVs to prevent obvious mirroring
		#if defined(TRIPLANAR_UV_OFFSET)
			uvY += 0.33;
			uvZ += 0.67;
		#endif

			// minor optimization of sign(). prevents return value of 0
			half3 axisSign = IN.worldNormal < 0 ? -1 : 1;

			// flip UVs horizontally to correct for back side projection
		#if defined(TRIPLANAR_CORRECT_PROJECTED_U)
			uvX.x *= axisSign.x;
			uvY.x *= axisSign.y;
			uvZ.x *= -axisSign.z;
		#endif

			/// *** Right nd Left side *** ///

			fixed3 colX = tex2D(_MainTex, uvX).rgb * triblend.x;
			half3 normX = UnpackNormal( tex2D (_NormalMap, uvX));
			half occX = tex2D(_OcclusionMap, uvX).g * triblend.x;

			/// *** Up and Down side *** ///

			fixed3 colY = tex2D(_MainTex, uvY).rgb * triblend.y;
			half3 normY = UnpackNormal(tex2D(_NormalMap, uvY));
			half occY = tex2D(_OcclusionMap, uvY).g * triblend.y;

			/// *** Forward and Back side *** ///
			fixed3 colZ = tex2D(_MainTex, uvZ).rgb * triblend.z;
			half3 normZ = UnpackNormal( tex2D (_NormalMap, uvZ));
			half occZ = tex2D(_OcclusionMap, uvZ).g * triblend.z;

			/// ***  *** ///
			fixed3 col = colX + colY + colZ;
			half occ = LerpOneTo(occX + occY + occZ, _OcclusionStrength);

			// flip normal maps' x axis to account for flipped UVs
		#if defined(TRIPLANAR_CORRECT_PROJECTED_U)
			normX.x *= axisSign.x;
			normY.x *= axisSign.y;
			normZ.x *= -axisSign.z;
		#endif

			half3 absVertNormal = abs(IN.worldNormal);

			// swizzle world normals to match tangent space and apply reoriented normal mapping blend
			normX = blend_rnm(half3(IN.worldNormal.zy, absVertNormal.x), normX);
			normY = blend_rnm(half3(IN.worldNormal.xz, absVertNormal.y), normY);
			normZ = blend_rnm(half3(IN.worldNormal.xy, absVertNormal.z), normZ);

			// apply world space sign to tangent space Z
			normX.z *= axisSign.x;
			normY.z *= axisSign.y;
			normZ.z *= axisSign.z;

			// sizzle tangent normals to match world normal and blend together
			half3 worldNormal = normalize(
				(normX.zyx * triblend.x) +
				(normY.xzy * triblend.y) +
				(normZ.xyz * triblend.z)
				);

			if (_Test < 1)
			{
				o.Albedo = float3 (0.5, 0.5, 0.5);
				o.Normal = WorldToTangentNormalVector(IN, worldNormal) * half4 (_Normal, _Normal, 1, 1);
			} else
			if (_Test >= 1 && _Test < 2)
			{
				o.Albedo = fixed3 (triblend.x, triblend.y, triblend.z);
			} else {
				// set surface ouput properties
				o.Albedo = col.rgb;
				o.Metallic = _Metallic;
				o.Smoothness = _Glossiness;
				o.Occlusion = occ;

				// convert world space normals into tangent normals
				o.Normal = WorldToTangentNormalVector(IN, worldNormal) * half4 (_Normal, _Normal, 1, 1);
			}
		}
		ENDCG
	}
	FallBack "Diffuse"
}


Код тестового скрипта создающего куб:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Create_Test_Cube : MonoBehaviour
{
	[Header("Компоненты:")]
	public MeshRenderer mesh_ren;
	public MeshFilter mesh_fil;
	public Mesh mesh;

	[Space]
	public Material mat;

	[Header("Параметры")]
	public List<Vector3> vertices = new List<Vector3>();
	public List<int> triangles = new List<int>();
	public List<Vector2> uvs = new List<Vector2>();

	void Start()
	{
		GetMesh();
		CreateCube();
	}

	[ContextMenu("Create Cube")]
	public void CreateCube()
	{
		if(vertices.Count > 0)
			ClearMesh();

		CreateAllSide();
		SetMesh();
	}

	void GetMesh()
	{
		mesh_ren = this.gameObject.GetComponent<MeshRenderer>();
		if (mesh_ren == null)
			mesh_ren = this.gameObject.AddComponent<MeshRenderer>();
		mesh_ren.material = mat;

		mesh_fil = this.gameObject.GetComponent<MeshFilter>();
		if (mesh_fil == null)
			mesh_fil = this.gameObject.AddComponent<MeshFilter>();
	}

	void SetMesh()
	{
		mesh = new Mesh();
		mesh.name = "test_cube_mesh";
		mesh.SetVertices(vertices);
		mesh.SetTriangles(triangles, 0);
		mesh.SetUVs(0, uvs);
		mesh.RecalculateBounds();
		mesh.RecalculateNormals();
		mesh_fil.mesh = mesh;
	}

	void ClearMesh()
	{
		vertices.Clear();
		triangles.Clear();
		uvs.Clear();
	}

	/// ***  *** ///

	void CreateAllSide()
	{
		vertices.Add(new Vector3(-0.5f, -0.5f, -0.5f));
		vertices.Add(new Vector3(-0.5f,  0.5f, -0.5f));
		vertices.Add(new Vector3( 0.5f, -0.5f, -0.5f));
		vertices.Add(new Vector3( 0.5f,  0.5f, -0.5f));
		AddTrianglesSquare();

		vertices.Add(new Vector3( 0.5f,  0.5f,  0.5f));
		vertices.Add(new Vector3( 0.5f,  0.5f, -0.5f));
		vertices.Add(new Vector3(-0.5f,  0.5f,  0.5f));
		vertices.Add(new Vector3(-0.5f,  0.5f, -0.5f));
		AddTrianglesSquare();

		vertices.Add(new Vector3( 0.5f, -0.5f, -0.5f));
		vertices.Add(new Vector3( 0.5f,  0.5f, -0.5f));
		vertices.Add(new Vector3( 0.5f, -0.5f,  0.5f));
		vertices.Add(new Vector3( 0.5f,  0.5f,  0.5f));
		AddTrianglesSquare();

		vertices.Add(new Vector3( 0.5f, -0.5f,  0.5f));
		vertices.Add(new Vector3( 0.5f,  0.5f,  0.5f));
		vertices.Add(new Vector3(-0.5f, -0.5f,  0.5f));
		vertices.Add(new Vector3(-0.5f,  0.5f,  0.5f));
		AddTrianglesSquare();

		vertices.Add(new Vector3(-0.5f, -0.5f,  0.5f));
		vertices.Add(new Vector3(-0.5f,  0.5f,  0.5f));
		vertices.Add(new Vector3(-0.5f, -0.5f, -0.5f));
		vertices.Add(new Vector3(-0.5f,  0.5f, -0.5f));
		AddTrianglesSquare();

		vertices.Add(new Vector3( 0.5f, -0.5f, -0.5f));
		vertices.Add(new Vector3( 0.5f, -0.5f,  0.5f));
		vertices.Add(new Vector3(-0.5f, -0.5f, -0.5f));
		vertices.Add(new Vector3(-0.5f, -0.5f,  0.5f));
		AddTrianglesSquare();
	}
	void AddTrianglesSquare()
	{
		triangles.Add(vertices.Count - 4);	// 0
		triangles.Add(vertices.Count - 3);	// 1
		triangles.Add(vertices.Count - 2);	// 2

		triangles.Add(vertices.Count - 2);	// 2
		triangles.Add(vertices.Count - 3);	// 1
		triangles.Add(vertices.Count - 1);	// 3
	}
}


Я все еще новичок в шейдерах и может просто не вижу чего-то очевидного, но у меня не получилось найти проблему.
  • Вопрос задан
  • 34 просмотра
Решения вопроса 1
DalerLiss
@DalerLiss Автор вопроса
Ребенок в большом теле
Думаю многие здесь поймут чего стоит потратить несколько дней на поиски решения возникшей проблемы, а в итоге столкнуться с собственной забывчивостью / невнимательностью / замыленным взглядом (нужное подчеркнуть). В моем случае проблемой оказался последний вариант.
Что в основном проекте, что в специально созданном под поиск этой проблемы тестовом, я банально забыл пересчитывать касательные, и всего одна строчка RecalculateTangents решила все мои проблемы.
Считаю это замечательным окончанием дня... пойду разобью клавиатуру об лицо... или наоборот...
Всем желаю вовремя давать отдых глазам и мозгу.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы