unity-game-engine - Shader saturate() 在不应该创建渐变的情况下创建渐变

标签 unity-game-engine shader hlsl vertex-shader shaderlab

为 Unity 编写着色器,我不明白这里发生了什么。我有一个附有 Material 的球体。 Material 附加了一个着色器。着色器非常简单,它生成一些单纯形噪声,然后将其用作球体的颜色。

着色器代码:

Shader "SimplexTest"
{
    Properties
    {
      _SimplexScale("Simplex Scale", Vector) = (4.0,4.0,4.0,1.0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "SimplexNoise3D.hlsl"

            float3 _SimplexScale;

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 position : SV_POSITION;
                float4 color : COLOR0;
            };

            v2f vert (appdata v)
            {
                v2f o;
                float4 worldPosition = mul(unity_ObjectToWorld, v.vertex);
                o.position = UnityObjectToClipPos(v.vertex);

                float small = snoise(_SimplexScale * worldPosition);
                // o.color = small * 10; // Non-Saturated (A) Version
                o.color = saturate(small * 10); // Saturated (B) Version

                return o;
            }


            fixed4 frag (v2f i) : SV_Target
            {
                return i.color;
            }
            ENDCG
        }
    }
}

单纯形噪声函数来自Keijiro's Github .

非饱和 (A) 版本:
Version A

饱和(B)版本:
Version B

非饱和版本(版本 A)符合预期。将单纯形噪声乘以 10 倍,结果是一系列白色和黑色 Blob 。理论上,饱和度(版本 B)应该在 1 处切掉所有白色 Blob ,在 0 处切掉所有黑色 Blob 。但它似乎正在创建一个全新的渐变,我不知道为什么。

我认为我遗漏了一些关键假设,但我不清楚那是什么,因为数学似乎是正确的。

最佳答案

在灰色区域中,片段着色器的像素位于颜色 0 和 1 的顶点之间。这意味着输入会在这些值之间进行插值,从而创建灰色 i.color 值。


一个解决方案是首先,将网格更改为每个三角形都有单独的顶点,以便每个三角形的所有顶点都具有相同的法线。

然后,一旦每个三角形的所有顶点都共享相同的法线,,您就可以根据顶点着色器中的 SV_NORMAL 值来设置颜色,而不是转换后的 POSITION 值。

大致如下:

struct appdata
{
    float4 vertex : POSITION;
    float4 normal : NORMAL;
};

...

v2f vert (appdata v)
{
    v2f o;
    float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    o.position = UnityObjectToClipPos(v.vertex);

    float small = snoise(_SimplexScale * worldNormal);
    o.color = saturate(small * 10);

    return o;
}

然后,它应该按预期工作。


另一种可以帮助完成这项工作的方法(如果您想在版本 A 中保留一点插值)是让插值继续,但只需在片段着色器中进行饱和:

v2f vert (appdata v)
{
    v2f o;
    float4 worldPosition = mul(unity_ObjectToWorld, v.vertex);
    o.position = UnityObjectToClipPos(v.vertex);

    float small = snoise(_SimplexScale * worldPosition);
    o.color = small * 10;

    return o;
}


fixed4 frag (v2f i) : SV_Target
{
    return saturate(i.color);
}

在示例 A 中,您的问题在很大程度上没有发生的原因是它在小于或等于零的值和一个大的正数之间进行插值。由于在非常大的数字和零附近的数字之间插值的值通常会大于 1,因此您会得到很多渲染为白色的像素。

关于unity-game-engine - Shader saturate() 在不应该创建渐变的情况下创建渐变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59493474/

相关文章:

c# - Visual Studio 可以理解,但 Unity 不能?

c++ - OpenGL 缓冲区到属性

opengl - 如何将顶点缓冲区绑定(bind)到 Gfx-rs 中的统一数组?

c++ - shader.hlsl 文件导致错误?

opengl - 了解 dFdX 和 dFdY 的基础知识

android - 重新请求 Android/iOS 权限以防用户首先拒绝

c# - 图层 ID 无效

javascript - 如何通过触发器将对象的纹理更改为新纹理

opengl - 基于物理的着色器未产生预期结果

directx - 您是否对多个纹理使用多个 SamplerStates