Unity shader - 如何根据纹理贴图生成法线贴图
Posted
技术标签:
【中文标题】Unity shader - 如何根据纹理贴图生成法线贴图【英文标题】:Unity shader - How to generate normal map based on texture map 【发布时间】:2021-12-02 16:02:33 【问题描述】:我目前被困在我正在编写的着色器中。 我正在尝试创建一个雨水着色器。 我设置了 3 个模拟下雨的粒子系统,以及一个 相机来看看这个模拟。相机视图是我 用作纹理。在我的着色器中,我现在正在尝试制作法线贴图 从那个纹理贴图,但我不知道该怎么做。
Shader "Unlit/Rain"
Properties
_MainTex("Albedo (RGB)", 2D) = "white"
_Color("Color", Color) = (1,1,1,1)
_NormalIntensity("NormalIntensity",Float) = 1
SubShader
Tags "RenderType" = "Opaque"
LOD 100
Pass
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct VertexInput
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 normal : NORMAL;
float3 tangent : TANGENT;
;
struct VertexOutput
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float2 uv1 : TEXCOORD1;
float4 normals : NORMAL;
float3 tangentSpaceLight: TANGENT;
;
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
float _NormalIntensity;
VertexOutput vert(VertexInput v)
VertexOutput o;
o.normals = v.normal;
o.uv1 = v.uv;
o.vertex = UnityObjectToClipPos( v.vertex );
// o.uv = TRANSFORM_TEX( v.uv, _MainTex ); // used for texture
return o;
float4 frag(VertexOutput i) : COLOR
float4 col2 = tex2D(_MainTex, i.uv1);
return col2 * i.normals * 5;
ENDCG
This is what the camera sees. I set the TargetTexture for this camera to be a texture I created.
In my shader I then put that texture as an albedo property
所以我现在想做的是找到该纹理的法线来创建凹凸贴图。
【问题讨论】:
“基于”纹理贴图是什么意思?如果纹理贴图是一个渲染,那和法线有什么关系? @Ruzihm 我在下面发布了一个答案:) 下面的答案应该是这篇文章的答案,下次请edit the question 包括澄清信息。 :) 好吧,对不起。我是新手 :) 你有这个问题的答案吗? :) 你可以试试interpreting it as a height map。没有像高度图这样的东西,没有真正令人满意的方法来根据它的外观来猜测它的形状。特别是以适合着色器的快速方式。如果着色器位于粒子系统本身而不是渲染纹理上,则可以让粒子系统创建着色器可以使用的法线贴图,因为粒子系统知道粒子速度、液滴大小等信息。这可能会更容易. 【参考方案1】:看起来你的“TargetTexture”正在给你一个高度图。这是我发现的关于how to turn a height map into a normal map 的帖子。我已经将您最初拥有的代码与该论坛帖子的核心代码混合在一起,并将法线输出为颜色,以便您可以测试并查看其工作原理:
Shader "Unlit/HeightToNormal"
Properties
_MainTex("Albedo (RGB)", 2D) = "white"
_Color("Color", Color) = (1,1,1,1)
_NormalIntensity("NormalIntensity",Float) = 1
_HeightMapSizeX("HeightMapSizeX",Float) = 1024
_HeightMapSizeY("HeightMapSizeY",Float) = 1024
SubShader
Tags "RenderType" = "Opaque"
LOD 100
Pass
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct VertexInput
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 normal : NORMAL;
float3 tangent : TANGENT;
;
struct VertexOutput
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float2 uv1 : TEXCOORD1;
float4 normals : NORMAL;
//float3 tangentSpaceLight: TANGENT;
;
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
float _NormalIntensity;
float _HeightMapSizeX;
float _HeightMapSizeY;
VertexOutput vert(VertexInput v)
VertexOutput o;
o.uv = TRANSFORM_TEX( v.uv, _MainTex ); // used for texture
o.uv1 = v.uv;
o.normals = v.normal;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
float4 frag(VertexOutput i) : COLOR
float me = tex2D(_MainTex,i.uv1).x;
float n = tex2D(_MainTex,float2(i.uv1.x, i.uv1.y + 1.0 / _HeightMapSizeY)).x;
float s = tex2D(_MainTex,float2(i.uv1.x, i.uv1.y - 1.0 / _HeightMapSizeY)).x;
float e = tex2D(_MainTex,float2(i.uv1.x + 1.0 / _HeightMapSizeX,i.uv1.y)).x;
float w = tex2D(_MainTex,float2(i.uv1.x - 1.0 / _HeightMapSizeX,i.uv1.y)).x;
// defining starting normal as color has some interesting effects, generally makes this more flexible
float3 norm = _Color;
float3 temp = norm; //a temporary vector that is not parallel to norm
if (norm.x == 1)
temp.y += 0.5;
else
temp.x += 0.5;
//form a basis with norm being one of the axes:
float3 perp1 = normalize(cross(i.normals,temp));
float3 perp2 = normalize(cross(i.normals,perp1));
//use the basis to move the normal i its own space by the offset
float3 normalOffset = -_NormalIntensity * (((n - me) - (s - me)) * perp1 + ((e - me) - (w - me)) * perp2);
norm += normalOffset;
norm = normalize(norm);
// it's also interesting to output temp, perp1, and perp1, or combinations of the float samples.
return float4(norm, 1);
ENDCG
要从高度贴图生成法线贴图,您尝试使用高度贴图中的定向变化率来得出一个法线向量,该向量可以使用 3 个浮点值(或颜色通道,如果它是一个图像)。您可以对您所在的图像点进行采样,然后在四个基本方向上离该点一小步。使用叉积来保证正交性,您可以定义一个基。使用图像上的定向步骤,您可以缩放两个基向量并将它们加在一起以找到“法线偏移”,这是近似高度图上值的定向变化的 3D 表示。基本上这是你的常态。
你可以看到我玩正常强度here 和“正常颜色”here 的效果。当这看起来适合您的用例时,您可以尝试将法线用作法线而不是彩色输出。
可能仍需要对值进行一些调整。祝你好运!
【讨论】:
我认为这和它所能得到的一样好,除非asker 可以让粒子生成器输出正常数据。很好的答案 非常感谢 :D 我会尝试实现它,看看它是否有效 :) @NSJacob 是我从相机接收到的“heightMapSampler”纹理吗?在那种情况下,“heightMapSizeY”和“heightMapSizeX”是什么?我也不完全理解你链接到的帖子,因为大部分着色器都不包含在那里:(你能帮忙吗? 是的,在本例中,'heightMapSampler' 将是您从相机接收到的纹理。 UV 是从 0 到 1 的浮点数,所以像IN.tex.y + 1.0/heightMapSizeY
这样的东西意味着“在给定 heightMapSizeX by heightMapSizeY
大小的纹理的情况下,在 y 轴上有效地采样一个像素的值。”
在编辑器中试了一下,做了一个小着色器玩具来玩。它将高度图图像转换为普通图像,这基本上是我理解的问题的核心。以上是关于Unity shader - 如何根据纹理贴图生成法线贴图的主要内容,如果未能解决你的问题,请参考以下文章
Unity Shaders学习笔记之通过修改UV坐标实现纹理贴图的滚动
Unity Shaders and Effects Cookbook (2-4) 压缩和混合纹理贴图:使用灰度图存储插值信息