我应该为 OpenGL 渲染器中的每种灯光类型使用不同的着色器吗

Jam*_*mes 1 opengl glsl

我正在为物理引擎制作一个相对简单的渲染(类似于这个)。我只是在学习 OpenGL 并且一直在关注本教程. 我希望我的渲染器能够处理从以下类型中选择的少量灯光:定向光、点光、聚光灯和区域光。我也想要使用阴影贴图的简单阴影。例如,一个场景可能包含两个聚光灯或一个定向光或一个点光源和一个聚光灯等。目前我有一个更大的着色器来处理所有灯光,但是现在我正在尝试阴影贴图,它看起来很轻最好(从模块化设计的角度来看)为每种灯光或至少每种灯光类型使用不同的着色器。我想知道从效率的角度来看,这是否是一个合理的想法。为了使这更具体,我当前的顶点看起来像:

#version 130 

in vec3 position;
in vec3 normal;
in vec2 atexture;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoord;
out vec4 FragPosLightSpace;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 lightView;
uniform mat4 lightProjection;

void main()
{
    gl_Position = projection * view * model * vec4(position.x, position.y, position.z, 1.0);
    FragPos = vec3(model * vec4(position, 1.0));
    Normal = normalize(normal);
    TexCoord = atexture;

    FragPosLightSpace = lightProjection * lightView * vec4(FragPos, 1.0f);
}
Run Code Online (Sandbox Code Playgroud)

和片段着色器:

#version 130

struct Material
{
    float shininess;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct DirLight
{
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct PointLight
{
    vec3 position;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct SpotLight {
    vec3 position;
    vec3 direction;
    float cutOff;
    float outerCutOff;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;       
};

struct AreaLight
{
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;   
};

out vec4 FragColor;

in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoord;
in vec4 FragPosLightSpace;

uniform Material material;
uniform DirLight dirLight;
uniform PointLight pointLight;
uniform SpotLight spotLight;
uniform AreaLight areaLight;

uniform vec3 cameraPos;

uniform sampler2D texture1;
uniform sampler2D shadowMap;

float CalcShadow(vec4 FragPosLightSpace);
vec3 CalcDirLight(Material material, DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(Material material, PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(Material material, SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcAreaLight(Material material, AreaLight light);

void main(void) 
{
    vec3 viewDir = normalize(cameraPos - FragPos);

    vec3 finalLight = vec3(0.0f, 0.0f, 0.0f);

    finalLight += CalcDirLight(material, dirLight, Normal, viewDir);

    finalLight += CalcPointLight(material, pointLight, Normal, FragPos, viewDir);

    finalLight += CalcSpotLight(material, spotLight, Normal, FragPos, viewDir);

    finalLight += CalcAreaLight(material, areaLight);

    FragColor = texture2D(texture1, TexCoord) * vec4(finalLight, 1.0f);
}


float CalcShadow(vec4 fragPosLightSpace)
{
    // only actually needed when using perspective projection for the light
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;

    // projCoord is in [-1,1] range. Convert it ot [0,1] range.
    projCoords = projCoords * 0.5 + 0.5;

    float closestDepth = texture(shadowMap, projCoords.xy).r;

    float currentDepth = projCoords.z;

    float bias = 0.005f;
    float shadow = currentDepth - bias > closestDepth  ? 1.0 : 0.0;

    return shadow;

}


vec3 CalcDirLight(Material material, DirLight light, vec3 normal, vec3 viewDir)

{
    vec3 lightDir = normalize(-light.direction);

    vec3 reflectDir = reflect(-lightDir, normal);

    float ambientStrength = 1.0f;
    float diffuseStrength = max(dot(normal, lightDir), 0.0);
    float specularStrength = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);

    float shadow = CalcShadow(FragPosLightSpace);

    vec3 ambient = light.ambient * material.ambient * ambientStrength;
    vec3 diffuse = (1.0f - shadow) * light.diffuse * material.diffuse * diffuseStrength;
    vec3 specular = (1.0f - shadow) * light.specular * material.specular * specularStrength;

    return (ambient + diffuse + specular);
}


vec3 CalcPointLight(Material material, PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);

    vec3 reflectDir = reflect(-lightDir, normal);

    float ambientStrength = 1.0f;
    float diffuseStrength = max(dot(normal, lightDir), 0.0);
    float specularStrength = pow(max(dot(viewDir, reflectDir), 0.0f), material.shininess);

    float attenuation = 1.0f / (1.0f + 0.01f*pow(length(light.position - fragPos), 2));

    vec3 ambient = light.ambient * material.ambient * ambientStrength;
    vec3 diffuse = light.diffuse * material.diffuse * diffuseStrength;
    vec3 specular = light.specular * material.specular * specularStrength;

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;

    return vec3(ambient + diffuse + specular);
}


vec3 CalcSpotLight(Material material, SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);

    vec3 reflectDir = reflect(-lightDir, normal);

    float ambientStrength = 0.05f;
    float diffuseStrength = max(dot(normal, lightDir), 0.0);
    float specularStrength = pow(max(dot(viewDir, reflectDir), 0.0f), material.shininess);

    float attenuation = 1.0f / (1.0f + 0.01f*pow(length(light.position - fragPos), 2));

    float theta = dot(lightDir, normalize(-light.direction));
    float epsilon = light.cutOff - light.outerCutOff;
    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0f, 1.0f);

    vec3 ambient = light.ambient * material.ambient * ambientStrength;
    vec3 diffuse = light.diffuse * material.diffuse * diffuseStrength;
    vec3 specular = light.specular * material.specular * specularStrength;

    ambient *= attenuation * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation * intensity;

    return vec3(ambient + diffuse + specular);
}


vec3 CalcAreaLight(Material material, AreaLight light)
{
    // return vec3(0.0f, 0.0f, 0.0f);
    return vec3(2*material.ambient);
}
Run Code Online (Sandbox Code Playgroud)

我想要做的是将每种灯光类型分离到不同的着色器,这样我就不会有一个“ubershader”了,我会有一个 directionalLight 着色器和一个聚光灯着色器等等。这是个好主意吗?我特别担心为每个渲染调用多次切换着色器可能会很昂贵?

Mic*_* IV 5

您的问题太广泛,不适合 SO 格式。但是我会尝试回答它,主要是因为初学者经常询问引擎编程。要为照明和阴影操作不同的着色器设置,您有 2 个标准做法:

  1. “优步着色器”

这背后的想法是您将所有可能的情况嵌入到此着色器中。例如,您希望能够渲染最多 4 个光源(我在这里谈论的是前向渲染),因此您插入一个for具有最大灯数的循环,然后传递一个统一的(场景中的灯数) ) 来实时告诉循环要迭代多少次。然后,如果您启用阴影通道,您还将一个统一传递到超级着色器以激活阴影贴图采样的“if”条件。正如您已经看到的,这种方式非常低效。您最终将在整个着色器中产生复杂的分支,并且必须在运行时提交多个统一体以更改着色器状态。所有这些都会影响性能和可用性。好吧,您可以通过使用 OpenGL 4.0 稍微简化一下子程序。但一般来说 - 不要这样做。

  1. 着色器排列

这是一种非常普遍的行业方式,虽然设计和设置这样的系统更复杂,但从长远来看是值得的。这个想法是你根据运行时的用例场景配置你的着色器代码(或者如果你有可用的离线着色器编译器,那么你甚至可以在编译时这样做),所以最后你会得到一个着色器字符串包含特定渲染设置的代码。例如,如果您的场景有 2 个灯光 + 阴影,并且可渲染对象的材质使用漫反射和法线贴图,那么您可以为该材质配置着色器以生成处理 2 个灯光、阴影贴图、漫反射和法线贴图采样的代码。在这里详细写下如何设计和编码这样一个系统会花费我太多的时间和空间。但一般来说,你写了一种着色器模板,充满了不同排列的预处理器标志。您为特定排列类型注入预处理器标志,然后编译着色器和着色器程序。在 Unity3D 和 Unreal 等一流游戏引擎中,所有可能的着色器排列都已在创作期间在编辑器中生成。如果您使用自己的引擎,只需在运行时组合所需的排列并将其放入着色器编译器。使用长着色器字符串时,您会注意到在线编译期间会出现轻微冻结,但如果您缓存并重用已编译的着色器程序排列,您会没事的。在创作期间,所有可能的着色器排列都已在编辑器中生成。如果您使用自己的引擎,只需在运行时组合所需的排列并将其放入着色器编译器。使用长着色器字符串时,您会注意到在线编译期间会出现轻微冻结,但如果您缓存并重用已编译的着色器程序排列,您会没事的。在创作期间,所有可能的着色器排列都已在编辑器中生成。如果您使用自己的引擎,只需在运行时组合所需的排列并将其放入着色器编译器。使用长着色器字符串时,您会注意到在线编译期间会出现轻微冻结,但如果您缓存并重用已编译的着色器程序排列,您会没事的。

奖金部分

您也可以按照您的建议执行此操作 - 预先构建着色器的不同变体,这实际上是我的第 2 种方法。但是您的提议是有问题的,因为如果您将单个灯光渲染逻辑包装到单独的程序中,那将意味着在具有 2 个光源的场景的情况下:

1 - 使用第一个光源渲染对象。

2 - 使用第二个光源渲染对象。

将两帧合成为最终结果。这已经需要 3 次渲染通道,并将更多地带延迟着色的方向,这是一项非常先进的技术,并不总是您需要的,除非您的计划是开发一个引擎来处理大量的几何体和光源。