2017-07-30 3 views
0

Je rends un rendu relativement simple pour un moteur physique (quelque chose de similaire à this). Je suis en train d'apprendre OpenGL et j'ai suivi cette tutorial. Je veux que mon moteur de rendu puisse manipuler un petit nombre de lumières choisies parmi les types: directionnel, point, projecteur, et lumière de secteur. Aussi, je veux des ombres simples en utilisant des cartes d'ombre. Ainsi, par exemple, une scène peut contenir deux projecteurs ou une lumière directionnelle ou une lumière ponctuelle et un projecteur. Actuellement, j'ai un plus grand shader qui gère toutes les lumières ensemble, mais maintenant que j'expérimente avec des cartes d'ombre, il semble léger mieux (du point de vue de la conception modulaire) pour avoir un shader différent pour chaque lumière ou au moins chaque type de lumière. Je me demande si c'est une idée raisonnable du point de vue de l'efficacité. Pour rendre cela plus concret mon sommet actuel ressemble:Dois-je utiliser un shader différent pour chaque type de lumière dans le rendu 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); 
} 

et fragment shader:

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

Ce que je voudrais faire est séparé chaque cendré vers un shader différent au lieu d'avoir un "ubershader" J'aurais un directionnel Light shader et un spotlight shader etc. Est-ce une bonne idée? En particulier, je suis inquiet que le changement de shaders plusieurs fois pour chaque appel de rendu peut être coûteux?

+0

Une réponse à cette question sera très large et rien dans la vidéo (la vidéo liée à votre question) n'est "* relativement simple *". – Rabbid76

+0

La physique dans la vidéo est complexe, le moteur de rendu, je ne pense pas? Vous avez peut-être raison de dire que la réponse serait large (bien que je ne le savais pas). L'utilisation de plusieurs shaders dans chaque passe de rendu est-elle une bonne idée? Ou est-ce trop dépendant d'autres facteurs? – James

+0

Désolé, vous parliez peut-être de rendre l'eau complexe. Permettez-moi de simplifier le problème en disant que je n'utiliserai que des corps rigides et que je ne fais que rendre un tas d'objets. Je vais inclure de l'eau dans mon moteur en utilisant des cubes de marche et du SPH mais nous pouvons supposer que ce sont des corps rigides pour cette question. – James

Répondre

2

Votre question est trop large et ne correspond pas au format SO. Cependant j'essaierai d'y répondre, surtout parce que les débutants en font souvent la programmation. Pour manipuler différentes configurations de shaders pour l'éclairage et l'ombrage vous avez 2 pratiques standard:

  1. « Uber shaders »

L'idée derrière cela est que vous avez tous les cas possibles embarqués dans ce shader. Donc, par exemple, vous voulez être capable de rendre jusqu'à 4 sources de lumière (je parle ici de forward-rendering), donc vous insérez une boucle for avec le nombre maximum de lumières, puis passez un uniforme (nombre de lumières dans la scène) pour indiquer à la boucle en temps réel le nombre de fois à parcourir. Ensuite, si vous activez la passe d'ombre, vous passez également un uniforme dans le uber-shader pour activer une condition "if" pour l'échantillonnage de cartes d'ombre. Comme vous pouvez déjà le voir, cette manière est très inefficace. Vous allez vous retrouver avec des embranchements complexes partout dans votre shader, et devrez soumettre plusieurs uniformes pendant l'exécution pour changer l'état du shader. Tout cela a un impact sur les performances et la convivialité. Eh bien, vous pouvez simplifier un peu, en utilisant OpenGL 4.0 subroutines. Mais en général, ne le faites pas.

  1. permutations Shader

C'est une façon courante tout à fait de l'industrie et alors qu'il est plus complexe à concevoir et à mettre en place un tel système, il est rentable sur le long courir. L'idée est de configurer le code de votre shaders en fonction du scénario d'utilisation en cours d'exécution (ou si vous avez un compilateur de shader offline disponible, vous pouvez même le faire à la compilation), donc à la fin vous obtenez une chaîne de shader contient le code pour l'installation de rendu spécifique. Par exemple, si votre scène a 2 lumières + ombres, et un matériau un objet utilisable utilise une carte diffuse et normale, alors vous configurez les shaders pour ce matériau pour générer un code pour gérer 2 lumières, l'ombrage, l'échantillonnage des cartes diffuses et normales. Il me faudrait trop de temps et d'espace pour écrire en détail comment concevoir et coder un tel système. Mais en général, vous écrivez une sorte de modèles de shader, plein de drapeaux de pré-processeur pour différentes permutations. Vous injectez des drapeaux de pré-processeur pour un type de permutation spécifique, puis vous compilez les shaders et le programme shader.Dans les moteurs de jeu de premier ordre comme Unity3D et Unreal, toutes les permutations de shader possibles sont déjà générées dans l'éditeur au moment de la création. Si vous lancez votre propre moteur, il suffit de composer la permutation requise pendant l'exécution et de le lancer dans le compilateur shader. Avec les longues chaînes de shader, vous remarquerez un léger gel lors de la compilation en ligne, mais si vous mettez en cache puis réutilisez les permutations déjà compilées des programmes de shaders, tout ira bien.

Bonus partie

Vous pouvez aussi le faire que vous proposiez - Prebuild les différentes variantes de shaders, ce qui est effectivement mon numéro 2 approche. Mais votre proposition est problématique, car si vous enroulez une seule logique de rendu de lumière dans un programme séparé, cela signifierait dans le cas d'une scène avec 2 sources lumineuses:

1 - Rendu de l'objet avec la première source de lumière.

2 - Rendu de l'objet avec la deuxième source de lumière.

Composez deux images dans le résultat final. Cela nécessite déjà 3 passes de rendu et vous emmène plus vers la direction de deferred shading, qui est une technique assez avancée et pas toujours ce dont vous avez besoin à moins que votre plan soit de développer un moteur pour traiter d'énormes quantités de géométrie et de sources lumineuses.

+0

Merci de votre réponse. Connaissez-vous de bonnes ressources qui donnent plus de détails sur la conception des shaders et des renderers, en particulier la méthode de permutation des shaders que vous décrivez? J'ai déjà entendu parler de la méthode d'ombrage différée, mais je ne pense pas que ce soit approprié pour mon projet, car ce serait plus compliqué que je ne l'aurais fait car je n'aurai que peu de lumières et généralement mon appareil pointera sur une chose le temps. – James

+0

@James J'avais l'habitude d'apprendre des projets open source. Essayez-le –