normalMap Shader

本文将为您介绍如何使用Unity3D进行游戏开发,包括游戏引擎的基础知识、场景搭建、物体操作、脚本编程等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

float4 AmbientColor : Ambient
<
    string UIName = "Ambient Color";
> = {0.25f, 0.25f, 0.25f, 1.0f};


float4 DiffuseColor : Diffuse
<
    string UIName = "Diffuse Color";
> = {1.0f, 1.0f, 1.0f, 1.0f};


texture diffuseMap : DiffuseMap
<
    string name = "default_color.dds";
 string UIName = "Diffuse Texture";
    string TextureType = "2D";
>;


texture normalMap : NormalMap
<
    string name = "default_bump_normal.dds";
 string UIName = "Normal Map";
    string TextureType = "2D";
>;


/************** light info **************/

float4 light1Pos : POSITION
<
 string UIName = "Light Position";
 string Object = "PointLight";
 string Space = "World";
 int refID = 0;
> = {100.0f, 100.0f, 100.0f, 0.0f};


float4 light1Color : LIGHTCOLOR
<
 int LightRef = 0;
> = { 1.0f, 1.0f, 1.0f, 0.0f };

/****************************************************/
/********** SAMPLERS ********************************/
/****************************************************/

sampler2D diffuseMapSampler = sampler_state
{
 Texture = <diffuseMap>;
 MinFilter = Linear;
 MagFilter = Linear;
 MipFilter = Anisotropic;
 ADDRESSU = WRAP;
    ADDRESSV = WRAP;
};

sampler2D normalMapSampler = sampler_state
{
 Texture = <normalMap>;
 MinFilter = Linear;
 MagFilter = Linear;
 MipFilter = Anisotropic;
 ADDRESSU = WRAP;
    ADDRESSV = WRAP;
};


/***********************************************/
/*** automatically-tracked "tweakables" ********/
/***********************************************/

float4x4 WorldViewProjection  : WorldViewProjection  < string UIWidget = "None"; >;
float4x4 WorldInverseTranspose  : WorldInverseTranspose < string UIWidget = "None"; >;
float4x4 ViewInverse    : ViewInverse    < string UIWidget = "None"; >;
float4x4 World      : World     < string UIWidget = "None"; >;


/****************************************************/
/********** CG SHADER FUNCTIONS *********************/
/****************************************************/

// input from application
 struct a2v {
 float4 position  : POSITION;
 float2 texCoord  : TEXCOORD0;
 float3 normal  : NORMAL;
 float3 binormal  : BINORMAL;
 float3 tangent  : TANGENT;
};


// output to fragment program
struct v2f {
        float4 position      : POSITION;
  float2 texCoord      : TEXCOORD0;
  float3 lightVec     : TEXCOORD1;
  float3 worldNormal  : TEXCOORD2;
  float3 worldBinormal : TEXCOORD3;
  float3 worldTangent  : TEXCOORD4;
};

/**************************************/
/***** VERTEX SHADER ******************/
/**************************************/

v2f v(a2v In, uniform float4 lightPosition)
{
 v2f Out;                //create the output struct
    Out.worldNormal = mul(In.normal, WorldInverseTranspose).xyz;  //put the normal in world space pass it to the pixel shader
    Out.worldBinormal = mul(In.binormal, WorldInverseTranspose).xyz; //put the binormal in world space pass it to the pixel shader
    Out.worldTangent = mul(In.tangent, WorldInverseTranspose).xyz;  //put the tangent in world space pass it to the pixel shader
    float3 worldSpacePos = mul(In.position, World);      //put the vertex in world space
    Out.lightVec = lightPosition - worldSpacePos;      //create the world space light vector and pass it to the pixel shader
 Out.texCoord.xy = In.texCoord;          //pass the UV coordinates to the pixel shader
    Out.position = mul(In.position, WorldViewProjection);    //put the vertex position in clip space and pass it to the pixel shader
    return Out;
}


/**************************************/
/***** FRAGMENT PROGRAM ***************/
/**************************************/

float4 f(v2f In,uniform float4 lightColor) : COLOR
{
  //fetch the diffuse and normal maps
  float4 ColorTexture = tex2D(diffuseMapSampler, In.texCoord.xy);    //the tex2d function takes the texture sampler and the texture coordinates and returns the texel color at that point
  float3 normal = tex2D(normalMapSampler, In.texCoord).xyz * 2.0 - 1.0;   //the normal must be expanded from 0-1 to -1 to 1

  //create tangent space vectors
  float3 Nn = In.worldNormal;
  float3 Bn = In.worldBinormal;
  float3 Tn = In.worldTangent;
 
 
  //offset world space normal with normal map values
  float3 N = (normal.z * Nn) + (normal.x * Bn) + (normal.y * -Tn);    //we use the values of the normal map to tweek surface normal, tangent, and binormal
  N = normalize(N);                //normalizing the result gives us the new surface normal
 
  //create lighting vectors - view vector and light vector
  float3 L = normalize(In.lightVec.xyz);          //the light vector must be normalized here so all vectors will be normalized
 
  //lighting
 
  //ambient light
  float4 Ambient = AmbientColor * ColorTexture;        //To create the ambient term, we multiply the ambient color and the diffuse texture
 
   
  //diffuse light
  float4 diffuselight = saturate(dot(N,L)) * lightColor;         //To get the diffuse light value we calculate the dot product between the light vector and the normal        
  float4 Diffuse = DiffuseColor * ColorTexture * diffuselight;        //To get the final diffuse color we multiply the diffuse color by the diffuse texture

  return Diffuse + Ambient;
}

/****************************************************/
/********** TECHNIQUES ******************************/
/****************************************************/

technique regular
{
    pass one
    {  
 VertexShader = compile vs_1_1 v(light1Pos);         //here we call the vertex shader function and tell it we want to use VS 1.1 as the profile     
 ZEnable = true;                //this enables sorting based on the Z buffer
 ZWriteEnable = true;              //this writes the depth value to the Z buffer so other objects will sort correctly this this one
 CullMode = CW;                //this enables backface culling.  CW stands for clockwise.  You can change it to CCW, or none if you want.
 AlphaBlendEnable = false;             //This disables transparency.  If you make it true, the alpha value of the final pixel shader color will determine how transparent the surface is.
 PixelShader = compile ps_2_0 f(light1Color);        //here we call the pixel shader function and tell it we want to use the PS 2.0 profile
    }
}

<think>我们想要在URP中使用通道图(例如一张纹理的R、G、B通道)来控制两个法线贴图的融合。根据引用[1]和[2]中的思路,我们可以这样设计: 引用[1]提到:使用一个通道(如B通道)作为混合度,0和1分别代表两个贴图的完全显示。这样我们可以用一张控制图的某个通道来控制两个法线贴图的混合。 引用[2]提到:法线贴图是记录高精度模型法线的贴图,用于低精度模型上增强细节。 具体步骤: 1. 准备两个法线贴图(NormalMap1, NormalMap2)和一张控制图(ControlMap)。控制图中我们选择其中一个通道(比如B通道)作为混合因子。 2. 在Shader中,我们需要对两个法线贴图进行采样,然后根据控制图对应位置的通道值进行混合。 注意:法线贴图在采样后存储的是切线空间下的法线向量,范围在[-1,1](经过UnpackNormal函数转换后)。混合时不能直接线性混合,因为法线是方向向量,需要保证结果仍然是单位向量。 混合方法: 我们可以使用线性插值(lerp)来混合两个法线向量,但要注意这样混合后的向量长度可能不是1,所以需要重新归一化(normalize)。 在URP中,我们可以通过编写自定义的Surface Shader或者使用Shader Graph来实现。 下面分别给出两种方式的实现思路: 一、通过编写HLSL Shader代码实现: 1. 在Properties中定义两个法线贴图和控制图: _NormalMap1 ("Normal Map 1", 2D) = "bump" {} _NormalMap2 ("Normal Map 2", 2D) = "bump" {} _ControlMap ("Control Map (RGB)", 2D) = "white" {} 2. 在顶点着色器中计算UV和切线空间到世界空间的转换矩阵(如果需要在世界空间计算光照,则通常需要将法线转换到世界空间)。 3. 在片段着色器中: - 采样两个法线贴图,使用UnpackNormal函数(或者URP中的UnpackNormalScale)将其转换为切线空间中的法线向量。 - 采样控制图,选择其中一个通道(比如B通道)作为混合因子blendFactor。 - 使用lerp混合两个法线向量:float3 blendedNormal = lerp(normal1, normal2, blendFactor); - 归一化混合后的法线:blendedNormal = normalize(blendedNormal); - 将切线空间法线转换到世界空间(如果需要)。 二、通过Shader Graph实现: 1. 创建三个Texture2D类型的属性:NormalMap1, NormalMap2, ControlMap。 2. 使用两个Sample Texture2D节点分别采样两个法线贴图,注意将Type设置为Normal。 3. 使用一个Sample Texture2D节点采样控制图,然后使用Split节点分离出RGB通道,选择其中一个通道(比如B)作为混合因子。 4. 使用Lerp节点混合两个法线向量:将两个法线向量分别连接到A和B,混合因子连接到T。 5. 使用Normalize节点对混合后的法线进行归一化。 6. 将归一化后的法线连接到Fragment Shader的Normal输入。 注意:在Shader Graph中,如果后续需要将法线用于光照计算,通常需要将切线空间法线转换为世界空间法线。可以使用Normal Vector节点,将Space设置为Tangent,然后连接到Normalize节点,再连接到输出。但URP的Shader Graph中,我们通常直接输出切线空间法线(如果使用的是PBR Master节点,它会自动处理转换)。 另外,引用[4]中提到在编辑模式下实时修改控制图,这通常是在编辑工具中实现的,与运行时Shader无关。 下面给出一个简单的代码示例(URP的Unlit Shader,但包含法线混合): 注意:这个示例假设我们不需要光照,仅展示法线混合。如果需要光照,需要将混合后的法线转换到世界空间,并加入光照计算。 由于URP中光照计算通常使用Surface Shader的简化写法(通过Lighting.hlsl),我们可以这样写: 完整Shader代码示例: ```hlsl Shader "Custom/BlendNormals" { Properties { _ControlMap ("Control Map", 2D) = "white" {} _NormalMap1 ("Normal Map 1", 2D) = "bump" {} _NormalMap2 ("Normal Map 2", 2D) = "bump" {} _BlendChannel ("Blend Channel (R=0,G=1,B=2)", Int) = 2 // 默认使用B通道 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalRenderPipeline"} Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; }; struct Varyings { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 tangentWS : TEXCOORD2; float3 bitangentWS : TEXCOORD3; }; TEXTURE2D(_ControlMap); SAMPLER(sampler_ControlMap); TEXTURE2D(_NormalMap1); SAMPLER(sampler_NormalMap1); TEXTURE2D(_NormalMap2); SAMPLER(sampler_NormalMap2); int _BlendChannel; Varyings vert(Attributes IN) { Varyings OUT; OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.uv = IN.uv; // 计算世界空间的法线、切线和副切线 VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS, IN.tangentOS); OUT.normalWS = normalInput.normalWS; OUT.tangentWS = normalInput.tangentWS; OUT.bitangentWS = normalInput.bitangentWS; return OUT; } half4 frag(Varyings IN) : SV_Target { // 采样控制图 half4 control = SAMPLE_TEXTURE2D(_ControlMap, sampler_ControlMap, IN.uv); // 根据选择的通道获取混合因子 half blendFactor = 0; if (_BlendChannel == 0) blendFactor = control.r; else if (_BlendChannel == 1) blendFactor = control.g; else blendFactor = control.b; // 采样两个法线贴图(切线空间) half4 normalSample1 = SAMPLE_TEXTURE2D(_NormalMap1, sampler_NormalMap1, IN.uv); half4 normalSample2 = SAMPLE_TEXTURE2D(_NormalMap2, sampler_NormalMap2, IN.uv); // 解包法线(切线空间,范围在[-1,1]) half3 normalTS1 = UnpackNormal(normalSample1); half3 normalTS2 = UnpackNormal(normalSample2); // 混合法线 half3 blendedNormalTS = lerp(normalTS1, normalTS2, blendFactor); blendedNormalTS = normalize(blendedNormalTS); // 将切线空间法线转换到世界空间 float3 normalWS = TransformTangentToWorld(blendedNormalTS, half3x3(IN.tangentWS.xyz, IN.bitangentWS.xyz, IN.normalWS.xyz)); // 归一化世界空间法线(因为插值和变换可能影响长度) normalWS = normalize(normalWS); // 这里为了演示,将世界空间法线转换为颜色输出 return half4(normalWS * 0.5 + 0.5, 1); } ENDHLSL } } } ``` 注意:在实际光照计算中,我们通常需要将混合后的世界空间法线用于光照计算(如漫反射、镜面反射等)。 此外,如果不需要在片段着色器中进行世界空间转换(比如在顶点着色器转换),可以优化性能。但为了精确,通常在片段着色器中进行。 在Shader Graph中实现会更加直观,无需编写代码,通过节点连接即可。 最后,引用[3]提到在URP中处理法线时,可能会有一些宏和变量名可以复用,但在这个例子中我们直接编写了完整的转换。 以上就是在URP中使用通道图控制两个法线贴图融合的方法。 相关问题: 1. 如何优化法线混合的性能? 2. 在Shader Graph中如何实现法线贴图的混合? 3. 法线混合时除了线性插值,还有哪些混合方式?</think>### URP中使用通道图控制双法线贴图融合的完整指南 在URP中实现通道图控制的双法线贴图融合,需要结合纹理采样、通道提取和法线混合技术。以下是详细实现方案: #### 一、核心原理与资源准备 1. **通道图控制原理**: - 使用RGB通道分别存储控制信息(如引用[1]所述) - 典型分配方案: - R通道:第一张法线贴图权重(0-1) - G通道:第二张法线贴图权重(0-1) - B通道:混合曲线控制参数(可选) - 最终混合公式: $$ \text{result} = \text{normal1} \times \text{red} + \text{normal2} \times \text{green} $$ 2. **资源准备**: ```hlsl // Shader属性声明 _NormalMap1("Normal Map 1", 2D) = "bump" {} _NormalMap2("Normal Map 2", 2D) = "bump" {} _ControlMap("Control Map (R=Norm1, G=Norm2)", 2D) = "white" {} _BlendSharpness("Blend Sharpness", Range(0, 5)) = 1 ``` #### 二、Shader实现关键步骤 1. **纹理采样与解包**: ```hlsl // 在片段着色器中 half4 control = SAMPLE_TEXTURE2D(_ControlMap, sampler_ControlMap, uv); half3 normal1 = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap1, sampler_NormalMap1, uv)); half3 normal2 = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap2, sampler_NormalMap2, uv)); ``` 2. **通道提取与混合**: ```hlsl // 增强混合曲线控制(如引用[3]提到的阈值处理) half blendFactorR = pow(control.r, _BlendSharpness); half blendFactorG = pow(control.g, _BlendSharpness); // 归一化权重(确保总和为1) half total = max(blendFactorR + blendFactorG, 0.001); half weight1 = blendFactorR / total; half weight2 = blendFactorG / total; // 法线混合 half3 blendedNormal = normal1 * weight1 + normal2 * weight2; blendedNormal = normalize(blendedNormal); // 关键归一化[^2] ``` 3. **集成到URP光照**: ```hlsl // 在URP的PBR光照函数中 InputData inputData; inputData.normalWS = TransformTangentToWorld( blendedNormal, half3x3(worldTangent, worldBitangent, worldNormal) ); // 标准URP光照计算 half4 color = UniversalFragmentPBR(inputData, surfaceData); ``` #### 三、Shader Graph可视化实现 1. **节点图结构**: ``` [ControlMap采样] → [分离RGB] → [幂运算(BlendSharpness)] ↘ [权重归一化] → [Lerp混合节点] ← [法线贴图1采样] ↘ [法线贴图2采样] ``` 2. **关键节点配置**: - 使用**Sample Texture 2D**节点采样三张纹理 - **Separate RGB**节点提取控制通道 - **Power**节点实现混合锐度控制 - **Normal Blend**节点或**Vector Lerp**进行法线混合 - 最后连接到**Master Stack**的Normal输入口 #### 四、性能优化技巧 1. **移动端优化**: ```hlsl // 使用half精度替代float half3 blendedNormal = half3(0,0,0); // 合并采样指令(如引用[1]的采样优化) #ifdef SHADER_API_MOBILE half4 normals = SAMPLE_TEXTURE2D(_CombinedNormals, ...); normal1 = UnpackNormal(normals.xy); normal2 = UnpackNormal(normals.zw); #endif ``` 2. **内存优化**: - 将两张法线贴图打包到同一张RGBA贴图: - RGB通道:第一张法线 - Alpha通道:第二张法线(或高度图) - 控制图使用BC4压缩格式减少内存占用 #### 五、高级混合技术 1. **三向贴图混合**(如引用[1]所述): ```hlsl // 添加第三张法线贴图 half3 normal3 = UnpackNormal(...); half weight3 = control.b; // 三向混合公式 blendedNormal = normal1*weight1 + normal2*weight2 + normal3*weight3; ``` 2. **法线角度阈值**(如引用[3]所述): ```hlsl // 避免法线方向突变 half angleThreshold = 0.85; // cos(30°) if(dot(normal1, normal2) < angleThreshold) { blendedNormal = normalize(lerp(normal1, normal2, smoothstep(...))); } ``` #### 六、完整Shader代码片段 ```hlsl // URP混合法线Shader void BlendNormals_half( float2 uv, sampler2D controlMap, sampler2D normalMap1, sampler2D normalMap2, float blendSharpness, out half3 blendedNormal) { half4 control = tex2D(controlMap, uv); half3 n1 = UnpackNormal(tex2D(normalMap1, uv)); half3 n2 = UnpackNormal(tex2D(normalMap2, uv)); half w1 = pow(control.r, blendSharpness); half w2 = pow(control.g, blendSharpness); half total = max(w1 + w2, 0.001); blendedNormal = normalize(n1 * (w1/total) + n2 * (w2/total)); } ``` > **实践建议**:在编辑器中实时预览效果时,可使用引用[4]的射线检测技术动态更新控制图,通过`Physics.Raycast`获取UV坐标并实时修改控制图像素值,实现地形编辑般的可视化控制[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值