【问题标题】:Applying Spotlights Over Dark Ambient Light - HLSL - Monogame在黑暗的环境光上应用聚光灯 - HLSL - Monogame
【发布时间】:2019-07-12 19:41:58
【问题描述】:

我为我的 Monogame 项目编写了一个 HLSL 着色器,该着色器使用环境光照来创建昼夜循环。

#if OPENGL
    #define SV_POSITION POSITION
    #define VS_SHADERMODEL vs_3_0
    #define PS_SHADERMODEL ps_3_0
#else
    #define VS_SHADERMODEL vs_4_0_level_9_1
    #define PS_SHADERMODEL ps_4_0_level_9_1
#endif

sampler s0;

struct VertexShaderOutput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
    float2 TextureCoordinates : TEXCOORD0;
};


float ambient = 1.0f;
float percentThroughDay = 0.0f;

float4 MainPS(VertexShaderOutput input) : COLOR
{
    float4 pixelColor = tex2D(s0, input.TextureCoordinates);
    float4 outputColor = pixelColor;

    // lighting intensity is gradient of pixel position
    float Intensity = 1 + (1  - input.TextureCoordinates.y) * 1.3;
    outputColor.r = outputColor.r / ambient * Intensity;
    outputColor.g = outputColor.g / ambient * Intensity;
    outputColor.b = outputColor.b / ambient * Intensity;

    // sun set/rise blending 
    float exposeRed = (1 + (.39 - input.TextureCoordinates.y) * 8); // overexpose red
    float exposeGreen = (1 + (.39 - input.TextureCoordinates.y) * 2); // some extra green for the blue pixels
    float exposeBlue = (1 + (.39 - input.TextureCoordinates.y) * 6); // some extra blue 

    // happens over full screen
    if (input.TextureCoordinates.y < 1.0f) {

        float redAdder = max(1, (exposeRed * (percentThroughDay/0.25f))); // be at full exposure at 25% of day gone
        float greenAdder = max(1, (exposeGreen * (percentThroughDay/0.25f))); // be at full exposure at 25% of day gone
        float blueAdder = max(1, (exposeBlue * (percentThroughDay/0.25f))); // be at full exposure at 25% of day gone

        // begin reducing adders
        if (percentThroughDay >= 0.25f && percentThroughDay < 0.50f) {
            redAdder = max(1, (exposeRed * (1-(percentThroughDay - 0.25f)/0.25f)));
            greenAdder = max(1, (exposeGreen * (1-(percentThroughDay - 0.25f)/0.25f)));
            blueAdder = max(1, (exposeGreen * (1-(percentThroughDay - 0.25f)/0.25f)));
        }

        //mid day
        else if (percentThroughDay >= 0.50f && percentThroughDay < 0.75f) {
            redAdder = 1;
            greenAdder = 1;
            blueAdder = 1;
        }

        // add adders back for sunset
        else if (percentThroughDay >= 0.75f && percentThroughDay < 0.85f) {
            redAdder = max(1, (exposeRed * ((percentThroughDay - 0.75f)/0.10f)));
            greenAdder = max(1, (exposeGreen * ((percentThroughDay - 0.75f)/0.10f)));
            blueAdder = max(1, (exposeBlue * ((percentThroughDay - 0.75f)/0.10f)));
        }

        // begin reducing adders
        else if (percentThroughDay >= 0.85f) {
            redAdder = max(1, (exposeRed * (1-(percentThroughDay - 0.85f)/0.15f)));
            greenAdder = max(1, (exposeGreen * (1-(percentThroughDay - 0.85f)/0.15f)));
            blueAdder = max(1, (exposeBlue * (1-(percentThroughDay - 0.85f)/0.15f)));
        }

        outputColor.r = outputColor.r * redAdder;
        outputColor.g = outputColor.g * greenAdder;
        outputColor.b = outputColor.b * blueAdder;
    }

    return outputColor;
}

technique ambientLightDayNight
{
    pass P0
    {
        PixelShader = compile ps_2_0 MainPS();
    }
};

这在很大程度上是我想要的(虽然它肯定可以使用一些计算优化)。

但是,我现在正在考虑在我的游戏中添加聚光灯供玩家使用。我跟着this method 一起工作,我独立于ambientLight 着色器工作。这是一个使用 lightMask 的非常简单的着色器。

sampler s0;  

texture lightMask;  
sampler lightSampler = sampler_state{Texture = lightMask;};  

float4 PixelShaderLight(float2 coords: TEXCOORD0) : COLOR0  
{  
    float4 color = tex2D(s0, coords);  
    float4 lightColor = tex2D(lightSampler, coords);  
    return color * lightColor;  
}  


technique Technique1  
{  
    pass Pass1  
    {  
        PixelShader = compile ps_2_0 PixelShaderLight();  
    }  
}  

我现在的问题是同时使用这两个着色器。我目前的方法是将我的游戏场景绘制到渲染目标,应用环境光着色器,然后通过绘制游戏场景(使用环境光现在)到客户端屏幕,同时应用聚光灯着色器。

这带来了多个问题:

  • 在环境光完全遮住灯光周围的任何物体后应用聚光灯着色器,而实际上灯光周围的区域应该是环境光。
  • 在聚光灯着色器中计算的光强度(灯光的亮度)在“夜晚”时过于单调,因为它是根据环境光着色器的输出来计算灯光颜色的。

我尝试在聚光灯着色器之后应用环境光着色器,但这只会将大部分内容渲染为黑色,因为环境光是根据大部分黑色背景计算的。

我尝试在聚光灯着色器中添加一些代码,将黑色像素着色为白色,以显示环境光背景,但是仍然根据较暗的环境光计算光强度 - 导致光线非常暗淡。

另一个想法是只修改我的环境光着色器以将 lightMask 作为参数,而不是将环境光应用到光罩上标记的灯光。然后我可以使用聚光灯着色器来应用光的渐变并修改颜色。但我不确定是否应该将这两种看似独立的灯光效果塞进一个像素着色器中。当我尝试这个时,我的着色器也没有编译,因为算术操作太多。

所以我想问大家的问题是:

  • 我应该避免将多种效果塞进一个像素着色器吗?
  • 一般来说,我如何将聚光灯应用到可能是“暗”的环境光效果上?

编辑

我的解决方案 - 最终没有使用聚光灯着色器,但仍然使用文章中给出的纹理绘制光蒙版,然后将该光蒙版传递给此环境光着色器并偏移纹理渐变。

float4 MainPS(VertexShaderOutput input) : COLOR
{

    float4 constant = 1.5f;
    float4 pixelColor = tex2D(s0, input.TextureCoordinates);
    float4 outputColor = pixelColor;

    // lighting intensity is gradient of pixel position
    float Intensity = 1 + (1  - input.TextureCoordinates.y) * 1.05;
    outputColor.r = outputColor.r / ambient * Intensity;
    outputColor.g = outputColor.g / ambient * Intensity;
    outputColor.b = outputColor.b / ambient * Intensity;

    // sun set/rise blending  
    float gval = (1 - input.TextureCoordinates.y); // replace 1 with .39 to lock to 39 percent of screen (this is how it was before)
    float exposeRed = (1 + gval * 8); // overexpose red
    float exposeGreen = (1 + gval * 2); // some extra green
    float exposeBlue = (1 + gval * 4); // some extra blue 

    float quarterDayPercent = (percentThroughDay/0.25f);
    float redAdder = max(1, (exposeRed * quarterDayPercent)); // be at full exposure at 25% of day gone
    float greenAdder = max(1, (exposeGreen * quarterDayPercent)); // be at full exposure at 25% of day gone
    float blueAdder = max(1, (exposeBlue * quarterDayPercent)); // be at full exposure at 25% of day gone

    // begin reducing adders
    if (percentThroughDay >= 0.25f ) {
        float gradientVal1 = (1-(percentThroughDay - 0.25f)/0.25f);
        redAdder = max(1, (exposeRed * gradientVal1));
        greenAdder = max(1, (exposeGreen * gradientVal1));
        blueAdder = max(1, (exposeGreen * gradientVal1));
    }

    //mid day
    if (percentThroughDay >= 0.50f) {
        redAdder = 1;
        greenAdder = 1;
        blueAdder = 1;
    }

    // add adders back for sunset
    if (percentThroughDay >= 0.75f) {
        float gradientVal2 = ((percentThroughDay - 0.75f)/0.10f);
        redAdder = max(1, (exposeRed * gradientVal2));
        greenAdder = max(1, (exposeGreen * gradientVal2));
        blueAdder = max(1, (exposeBlue * gradientVal2));
    }

    // begin reducing adders
    if (percentThroughDay >= 0.85f) {

        float gradientVal3 = (1-(percentThroughDay - 0.85f)/0.15f);
        redAdder = max(1, (exposeRed * gradientVal3));
        greenAdder = max(1, (exposeGreen * gradientVal3));
        blueAdder = max(1, (exposeBlue * gradientVal3));
    }

    outputColor.r = outputColor.r * redAdder;
    outputColor.g = outputColor.g * greenAdder;
    outputColor.b = outputColor.b * blueAdder;

    // first check if we are in a lightMask light
    float4 lightMaskColor = tex2D(lightSampler, input.TextureCoordinates);
    if (lightMaskColor.r != 0.0f || lightMaskColor.g != 0.0f || lightMaskColor.b != 0.0f) 
    {
        // we are in the light so don't apply ambient light
        return pixelColor * (lightMaskColor + outputColor) * constant; // have to offset by outputColor here because the lightMask is pure black
    }

    return outputColor * pixelColor * constant; // must multiply by pixelColor here to offset the lightMask bounds. TODO: could try to restore original color by removing this multiplaction and factoring in more of an offset on ln 91
}

【问题讨论】:

    标签: xna shader monogame hlsl lighting


    【解决方案1】:

    要随心所欲地串灯,您需要一种不同的方法。正如您已经遇到的那样,仅在颜色上链接灯是行不通的,因为一旦颜色变成黑色,它就不能再突出显示了。处理多灯有两种典型的方法:前向着色和延迟着色。每种都有其优点和缺点,因此您需要查看哪种更适合您的情况。


    前向着色

    这种方法是您测试的一种方法,将所有照明计算填充到单个着色通道中。您将所有光强度加在一起以形成最终的光强度,然后将其与颜色相乘。

    优点是性能和简单性,缺点是灯光数量的限制和更复杂的着色器代码。


    延迟着色

    这种方法将单个灯光彼此分离,可用于绘制具有非常多灯光的场景。每个灯光都需要原始场景颜色(反照率)来计算其在最终图像中的部分。因此,您首先在没有任何光照的情况下将场景渲染到纹理(通常称为颜色缓冲区或反照率缓冲区)上。然后,您可以分别渲染每个灯光,将其与反照率相乘并将其添加到最终图像中。因此,即使在黑暗部分,原始颜色也会随着光线再次出现。

    优点是结构更简洁,可以使用大量灯光,即使形状不同。缺点是必须进行的额外缓冲区和绘图调用。

    【讨论】:

    • 我使用了前向着色来让它最终工作。我可能会重构逻辑以使用延迟着色来获得更简洁的代码。但我会在编辑中发布我目前的方法。感谢@Gnietschow 的信息。将此标记为答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-09
    • 2015-05-02
    相关资源
    最近更新 更多