我是否应该在OpenGL渲染器

时间:2017-07-30 18:32:08

标签: opengl glsl

我正在为物理引擎做一个相对简单的渲染(类似于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”,而是会有一个DirectionLight着色器和一个聚光灯着色器等。这是个好主意吗?特别是我担心每次渲染调用多次切换着色器可能会很昂贵?

1 个答案:

答案 0 :(得分:4)

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

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

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

      奖金部分

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

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

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

      将两个帧组合成最终结果。这已经需要3个渲染过程,并且你需要更多的方向deferred shading,这是一种非常先进的技术,并不总是你需要的,除非你的计划是开发一个引擎来处理大量的几何和光源