首页 > 解决方案 > 如何在 Unity 中将表面着色器转换为顶点/片段代码?

问题描述

我想知道如何将此表面着色器代码转换为顶点/片段杂注:

Shader "Custom/Diamond Opaque Test" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _SpecColor ("Specular Color", Color) = (0.5,0.5,0.5,1)
        _Shininess ("Shininess", Range (0.01, 1)) = 0.078125
        _RimPower ("Rim Power", Range(0,8.0)) = 3.0
        _ReflectColor ("Reflection Color", Color) = (1,1,1,0.5)
        [NoScaleOffset] _Cube ("Cubemap", CUBE) = "" {}
    }

    SubShader {

        CGPROGRAM
        #pragma surface surf BlinnPhong

        struct Input {
            float3 worldRefl;
            float3 viewDir;
        };

        samplerCUBE _Cube;
        fixed4 _Color;
        fixed4 _ReflectColor;
        half _Shininess;
        half _RimPower;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = _Color.rgb;
            o.Gloss = _Color.a;
            o.Specular = _Shininess;
            half rim = saturate(dot (normalize(IN.viewDir), o.Normal));
            o.Emission = texCUBE (_Cube, IN.worldRefl).rgb * _ReflectColor.rgb * pow(rim,_RimPower);;
        }
        ENDCG
    } 
    FallBack "Mobile/Diffuse"
}

上面的着色器代码是下图左侧的红色宝石:

没有影子

我的目标是看起来与右边的宝石相似。在此过程中,我遇到了一些挑战。左边的宝石是不透明的并且接受阴影。右边的宝石有正面透明但没有背面。然而,这不接受阴影。我必须在 let BlinnPhong 上制作宝石,以便它可以投射和接收阴影。

阴影

是否可以为正面和背面透明度创建不同的控件?还有一种方法可以让透明物体接受阴影吗?我想为宝石添加轮廓,以便可以在更远的地方看到它。有没有办法在同一个 Pass 中添加轮廓,还是必须有自己单独的 Pass?另外,如何使接收到的阴影变暗?所有这些要求都会对 GPU 造成负担吗?该游戏适用于移动平台。

我是 CG 编程的初学者。我的第一步是弄清楚将曲面编程转换为顶点/片段。任何例子都将非常感激。

我确实在资产商店购买了一些宝石资产,但其中一些可以投射阴影但无法接收或根本无法投射或接收。是什么赋予了?右侧的宝石着色器来自资产商店中购买的资产之一。我不想在未经他们许可的情况下在这个网站上分享他们的着色器脚本。

标签: unity3dshaderfragment-shadervertex-shader

解决方案


这实际上是一个相当复杂的过程,尽管我建议您从他们的网站下载统一着色器源代码开始。大多数实际着色在 UnityStandardBRDF.cginc、UnityPBSLighting.cginc 和 UnityStandardCore.cginc 中完成。如果你用谷歌搜索,你也可以在 github 上找到这些文件。

基本上,前向添加着色器的基本结构如下所示:

Shader "MyForwardAddShader"
{
    Properties
    {
        _MainTex("Some texture", 2D) = "white"{}
    }

    SubShader
    {
        Tags {"RenderType"="Geometry" "Queue" = "Geometry"}

        Pass 
        {
            // Forward Base pass - this applies main directional light and ambient
            Name "FORWARD"
            Tags { "LightMode" = "ForwardBase" }

            CGPROGRAM
            #pragma target 3.5

            #pragma multi_compile_fwdbase
            #pragma multi_compile_fog
            #pragma multi_compile_instancing

            #pragma vertex vert
            #pragma fragment frag

            // I Like to keep these in separate include files
            #include "MyForwardBase.cginc"

            ENDCG
        }

        Pass
        {
            // Forward Add pass - this is added once per extra light source

            Name "FORWARD_DELTA"
            Tags { "LightMode" = "ForwardAdd" }
            Blend SrcAlpha One
            Fog { Color (0,0,0,0) } // in additive pass fog should be black
            ZWrite Off
            ZTest LEqual

            CGPROGRAM
            #pragma target 3.5

            #pragma multi_compile_fwdadd_fullshadows
            #pragma multi_compile_fog

            #pragma vertex vert
            #pragma fragment frag

            #include "MyForwardAdd.cginc"

            ENDCG
        }
    }
}

请注意,您还需要一个 shadowcaster pass 来投射阴影(您可以复制标准着色器源代码中使用的 pass),如果您想使用烘焙照明,还需要一个 Meta pass。

像“MyForwardBase.cginc”这样的文件应该包括顶点输入和输出结构的定义,以及传递的顶点和片段函数。使用此设置,您还需要在那里定义制服(属性变量)。光照计算可能需要的一些变量:

// Calculate this in the vertex shader and normalize per fragment
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 viewDir = _WorldSpaceCameraPos.xyz - o.worldPos;

// In the ForwardBase pass, _WorldSPaceLightPos0.xyz stores the direction of the main directional light, instead of position. 
// Use this in the vertex function:
half3 mainLightColor = _LightColor0.rgb;
half3 mainLightDir = _WorldSpaceLightPos0.xyz;

对于 ForwardBase 通道,您需要包含“AutoLight.cginc”并在顶点函数中使用此宏:

COMPUTE_LIGHT_COORDS(o);

这反过来又要求您在 VertexOutput 结构中具有此宏:

UNITY_LIGHTING_COORDS(6,7) // 6, 7 can be any texture interpolator IDs, they will translate to TEXCOORD6 and TEXCOORD7 in this case

ForwardAdd 光衰减的工作原理如下:

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos.xyz); // Here, worldPos is stored in the vertex output struct and calculated per vertex
half3 lightColor = _LightColor0.rgb * atten;

还有一些宏用于确定像素是否在阴影中,您可以在 AutoLight.cginc 中找到它们。在 ForwardAdd 过程中,光的方向是按顶点计算的,如下所示:

o.lightDir      = _WorldSpaceLightPos0.xyz - o.worldPos.xyz * _WorldSpaceLightPos0.w;

就个人而言,我喜欢为每个特征创建小的 CGIncludes,例如反射、折射、三平面映射等,并且只要我需要它们就包含它们。我还倾向于在其自己的包含文件中保留一个名为“Lighting”的单独函数,因为光照计算在通道之间共享。此函数将所有变量(如 lightDir、viewDir、albedo、roughness 等)作为参数并返回片段的阴影颜色。

就像我说的,这是一个相当复杂的过程,这只是一个入门。实现照明功能本身就是一个故事,尽管查看 UnityStandardBRDF.cginc 可以帮助解决这个问题。我建议您从基本的 blinn-phong 模型开始进行调试和改进。


推荐阅读