【问题标题】:Should I use a different shader for each light type in OpenGL renderer我应该为 OpenGL 渲染器中的每种灯光类型使用不同的着色器吗
【发布时间】:2017-07-30 18:32:08
【问题描述】:

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

#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);
}

和片段着色器:

#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);
}

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

【问题讨论】:

  • 视频里的物理很复杂,我觉得渲染器不复杂?也许你是对的,答案是广泛的(尽管我不知道)。在每个渲染过程中使用多个着色器是否被认为是一个好主意?还是过于依赖其他因素?
  • 抱歉,您可能是指将水渲染为复杂的。让我通过说我将只使用刚体来简化问题,因此我只渲染一堆对象。我将使用行进立方体和 SPH 在我的引擎中加入水,但对于这个问题,我们可以假设它只是刚体。
  • 这取决于很多因素,你应该开始评估你想要构建什么样的渲染管道以及为什么。你想坚持前向渲染还是前进+或延迟风格之一(香草、平铺、集群)?您的目标平台是什么,对您来说重要的是什么(性能与简单性),项目的范围是什么(如您在未来需要哪些功能)?打个比方,最简单的解决方案是您已经拥有的解决方案。如果不是问题,请不要修复它。

标签: opengl glsl


【解决方案1】:

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

  1. “优步着色器”

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

  1. 着色器排列

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

奖金部分

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

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

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

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

【讨论】:

  • 感谢您的回复。您是否知道任何提供有关着色器和渲染器设计的更多详细信息的好资源,特别是您描述的着色器排列方法?我以前听说过延迟着色方法,但我认为它不适合我的项目,因为它比它的价值更麻烦,因为我只有几盏灯,而且通常我的相机大部分都会指向一个东西时间。
  • 如果每个光源都有自己的着色器,也可能会提到加法混合。但是,是的,这实际上将消除不做 deferred 的论点。 :)
猜你喜欢
  • 2014-09-25
  • 2012-05-20
  • 2012-02-27
  • 1970-01-01
  • 2016-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多