结合 Matcap 捕捉复杂光泽,与 Ramp 贴图实现视角相关的渐变色彩,最终Shader效果可呈现出类如甲壳虫鞘翅或蝴蝶翅膀上随观看角度变幻的流光溢彩。

🎨 核心原理与思路

这个Shader的核心,可以看作是两层效果的巧妙叠加:

  • 光影与高光 (Matcap层):Matcap的原理非常直接,它使用一张预先渲染好的法线贴图来“伪造”光线照射在多曲面物体上的复杂光影。通过把模型的面法线转换到视角空间,然后直接作为UV坐标去采样一张特殊的贴图(Matcap贴图),就能获得基础的、极具质感的光照和高光反射信息。这是我们“蝉翼/甲壳虫”质感的光影基础。

  • 相位色彩 (Ramp层):真实世界的薄膜干涉,是光线在薄膜两个表面反射后干涉的结果,其产生的色彩会随着我们视线与表面法线的夹角变化而改变。在Shader中,我们正是通过计算 法线方向与视线方向的点积(NdotV) 来模拟这个变量(NdotV = 1时,表示正对表面;NdotV = 0时,表示视线近乎与表面擦肩而过)。
    然后,我们拿这个值去采样一张精心设计的“彩虹色”渐变贴图(Ramp贴图),就能根据视角变化,为模型幻化出瑰丽的色彩。

最终的效果,就是将这两部分按比例混合(比如加法混合)的结果。

🗺️ 需要准备什么

  1. Matcap贴图:决定模型的基础光泽感。网上搜索 "nidorx/matcaps" 能发现大量的免费资源,也可以自己用3D软件渲染一张球体图。

  2. Ramp贴图:控制色彩变化的序列。你可以手动制作一张横向的彩虹渐变图,比如黑→紫蓝→青绿→橙红的顺序。在Unity的导入设置里,务必把 Texture Type 设为 Default,并把 Wrap Mode 改为 Clamp,防止颜色错误。

📝 完整Shader代码 (URP)

这里提供一个完整的Shader,针对Unity的通用渲染管线 (URP) 编写,包含基础颜色纹理支持、Matcap强度控制等关键功能。

Shader "Custom/MatCapRamp_Iridescence"
{
    Properties
    {
        // 本Shader主要使用的贴图属性
        _Color ("主颜色", Color) = (1,1,1,1)
        _MainTex ("基础纹理 (RGB)", 2D) = "white" {}
        [NoScaleOffset] _MatCap ("Matcap 贴图", 2D) = "black" {}
        _MatcapStrength ("Matcap 强度", Range(0, 2)) = 1.0
        
        _RampTex ("薄膜干涉 Ramp 贴图", 2D) = "white" {}
        _RampStrength ("薄膜干涉强度", Range(0, 2)) = 1.0
        _FresnelPower ("Fresnel 强度 (NdotV)", Range(0, 5)) = 0.5
        
        // 可选的光泽微调参数
        _Glossiness ("光泽度", Range(0, 1)) = 0.5
        
        // 以下是为了兼容标准材质球或部分光照模型而保留的占位属性,可以忽略
        [HideInInspector] _GlossMapScale ("", Float) = 1.0
        [HideInInspector] _GlossyReflections ("", Float) = 1.0
        [HideInInspector] _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { 
            "RenderType"="Opaque" 
            "RenderPipeline"="UniversalPipeline"
            "Queue"="Geometry"
        }
        LOD 300

        Pass
        {
            Name "ForwardLit"
            Tags{"LightMode" = "UniversalForward"}
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE

            // 引入URP的核心库和光照库
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            // 材质属性定义
            CBUFFER_START(UnityPerMaterial)
                float4 _Color;
                float4 _MainTex_ST;
                float _MatcapStrength;
                float _RampStrength;
                float _FresnelPower;
                float _Glossiness;
            CBUFFER_END

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            TEXTURE2D(_MatCap);
            SAMPLER(sampler_MatCap);
            TEXTURE2D(_RampTex);
            SAMPLER(sampler_RampTex);

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
                float2 texcoord : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normalWS : TEXCOORD1;
                float3 viewDirWS : TEXCOORD2;
                float3 positionWS : TEXCOORD3;
                float2 matcapUV : TEXCOORD4; // Matcap采样UV
            };
            
            // 计算Matcap UV的核心函数
            float2 GetMatcapUV(float3 normalWS)
            {
                // 将世界空间法线转换到观察空间
                float3 normalVS = TransformWorldToViewDir(normalWS);
                // 将坐标从 [-1, 1] 范围映射到 [0, 1] 的UV范围
                float2 uv = normalVS.xy * 0.5 + 0.5;
                return uv;
            }
            
            // 菲涅尔效应:根据法线和视线的夹角返回一个值,用于驱动Ramp采样
            float GetFresnel(float3 normalWS, float3 viewDirWS)
            {
                float NdotV = dot(normalWS, viewDirWS);
                // 使用 saturate 确保值在 0~1 范围,_FresnelPower 控制曲线
                float fresnel = pow(1.0 - saturate(NdotV), _FresnelPower);
                return fresnel;
            }

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                
                // 标准顶点变换
                VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.positionOS.xyz);
                OUT.positionCS = vertexInput.positionCS;
                OUT.positionWS = vertexInput.positionWS;
                
                // 法线和视线方向变换
                VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS);
                OUT.normalWS = normalInput.normalWS;
                OUT.viewDirWS = GetCameraPositionWS() - vertexInput.positionWS;
                
                // 传递UV
                OUT.uv = TRANSFORM_TEX(IN.texcoord, _MainTex);
                
                // 计算 Matcap UV
                OUT.matcapUV = GetMatcapUV(OUT.normalWS);
                
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                // ------ 基础纹理采样 ------
                half4 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv) * _Color;
                
                // ------ 1. Matcap 层:模拟光影和高光 ------
                half3 matcapColor = SAMPLE_TEXTURE2D(_MatCap, sampler_MatCap, IN.matcapUV).rgb;
                half3 matcapContribution = matcapColor * _MatcapStrength;
                
                // ------ 2. 薄膜干涉 (Ramp) 层:模拟视角相关色彩变化 ------
                // 获取视角相关的菲涅尔值
                float fresnelVal = GetFresnel(IN.normalWS, normalize(IN.viewDirWS));
                // 用菲涅尔值作为U坐标,采样Ramp贴图(V坐标固定为0.5)
                half3 rampColor = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, float2(fresnelVal, 0.5)).rgb;
                half3 interferColor = rampColor * _RampStrength;
                
                // ------ 3. 光照与环境光贡献(由URP提供简单光照,让效果更平实)-----
                // 获取主光源方向与颜色
                Light mainLight = GetMainLight();
                float3 lightDir = mainLight.direction;
                float3 lightColor = mainLight.color;
                
                // 简单的漫反射光照计算
                float NdotL = saturate(dot(IN.normalWS, lightDir));
                half3 diffuse = albedo.rgb * lightColor * NdotL;
                
                // 获取环境光颜色
                half3 ambient = SampleSH(IN.normalWS);
                
                // ------ 最终颜色混合 ------
                // 将光影Matcap层和相位Ramp层进行加法混合
                half3 finalColor = diffuse + ambient + matcapContribution + interferColor;
                
                // 简单的色调映射,防止颜色过曝
                finalColor = finalColor / (finalColor + 1.0);
                
                // 返回最终颜色
                return half4(finalColor, 1.0);
            }
            ENDHLSL
        }
    }
    FallBack "Universal Render Pipeline/Lit"
}
参数 作用 调整建议
Matcap 贴图 决定基础光泽感和高光分布 优先选择金属质感强、高光锐利的贴图,以模拟甲壳虫的坚硬亮泽感。
Matcap 强度 控制上述效果对最终颜色的影响 建议设置在 0.8 ~ 1.5 之间,以获得明显的光影和高光。
Ramp 贴图 预设所有视角变化下的颜色循环 可以尝试光谱循环或蓝-紫-绿-粉等冷暖色系对立明显的渐变序列。
薄膜干涉强度 控制彩虹色效果的显眼程度 根据美术需求调整,想要绚丽的甲壳虫效果可以设到 1.0 以上。
Fresnel 强度 影响“相位”色彩出现在高光中心还是边缘 0.5 左右通常能得到一个比较平均的效果,数值越大,绚丽的色彩越集中在边缘。
基础纹理 & 主颜色 提供底层颜色,增强质感 可以放置类似金属或布纹的纹理,让效果更加细腻。

🚀 扩展思路

这个基础版本可以再打磨得更进一步:

  • 混合模式:Matcap层与Ramp层不只有加法混合,尝试乘法混合也能看到一些有趣的风格化效果。

  • NPR风格化:结合Ramp贴图的风格,可以制作出完全不同的卡通渲染效果,或模拟出特殊材质的“X光照”效果。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐