Я делаю свой собственный движок для своей игры, и у меня было много проблем с шейдерами. Одним из самых раздражающих является то, что я не могу вычислить источники света, потому что условие во фрагментном шейдере никогда не выполняется, поэтому никогда не суммируются цвета различных материалов/светов. Кусок кода, в котором я обнаружил проблему, таков:
// Compute lights
vec4 totalColorLighting = vec4(0.0);
for (int indexComputeLights = 0; indexComputeLights < MAX_LIGHTS; indexComputeLights++)
{
if (indexComputeLights < numLights) {
numLights — это юниформа, передаваемая фрагментному шейдеру. Если я напишу totalColorLight = vec4(1,0);
вне оператора if, мои модели будут нарисованы белым цветом. Если я вставлю его внутрь, как я хочу, экран останется черным...
Короче говоря, код, который показывает белую модель:
for (int indexComputeLights = 0; indexComputeLights < MAX_LIGHTS; indexComputeLights++)
{
totalColorLight = vec4(1,0); // model is white, so here it´s entering
if (indexComputeLights < numLights) {
// code that never get executed
}
}
И код не работает:
for (int indexComputeLights = 0; indexComputeLights < MAX_LIGHTS; indexComputeLights++)
{
if (indexComputeLights < numLights) {
totalColorLight = vec4(1,0); // model is black, so here it´s NOT entering
}
}
Другой способ получить «обходной путь» — изменить «numLights» на постоянное число, например 2 или 1. Я пересмотрел переменные, в которых я передаю юниформ-значение шейдерам через отладчик eclipse, и оно равно 1, поэтому он должен входить в оператор if.
То, как я загружаю данные в эту униформу, таково:
int[] vecNumLights = new int[1];
vecNumLights[0] = numLights;
GLES20.glUniform1iv(gl_numLights_Uniform_Locator, 1, vecNumLights, 0);
Быстро взглянув, кто-нибудь знает, что я ошибаюсь? Вам нужно больше кода для копирования?
Кстати, я использую SDK 4.4.2 Api 19.
РЕДАКТИРОВАТЬ 1:
Я заметил, что indexComputeLights не равен нулю с самого начала. Если я напишу такое условие:
if (indexComputeLights > 0) totalColorLight = totalColorLight + vec4(0.05);
модель тем менее белая, чем выше состояние, я имею в виду, если состояние
if (indexComputeLights > 6)
цвет заканчивается более прозрачным и черным, просто в обратном порядке, как я понял. Что здесь произошло с for-unroll ?
РЕДАКТИРОВАТЬ2:
Я думаю, что моя проблема очень похожа на эту: http://www.opengl.org/discussion_boards/showthread.php/171366-problem-with-uniform-int-and-for-loop
Разница в том, что я не заметил, что цикл for был бесконечным.
РЕДАКТИРОВАТЬ3:
Я обнаружил, что numLights (который является униформой int) всегда равен нулю. Я не могу понять, что не так, потому что все остальные униформы загружаются нормально. Я разделяю единообразное имя между вершинными и фрагментными шейдерами, но в вершинных шейдерах, похоже, все работает нормально.
РЕДАКТИРОВАТЬ4:
Я хотел бы поделиться с вами полным кодом вершин и фрагментов.
Вершинный шейдер:
#pragma glsl
attribute vec3 position;
attribute vec3 normal;
attribute vec2 texCoord;
// matrices we'll need
uniform mat4 inversedTrasposedModelViewMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
// other uniforms, constant or data we are going to use
const int MAX_LIGHTS = 8;
struct LightSourceParameters {
mediump vec3 ambient;
mediump vec3 lightColor;
mediump vec4 position;
mediump float spotExponent;
mediump float spotCutoff; // (range: [0.0,90.0], 180.0)
mediump vec3 spotDirection;
mediump float constantAttenuation;
mediump float linearAttenuation;
mediump float quadraticAttenuation;
};
uniform LightSourceParameters LightSource[MAX_LIGHTS];
uniform lowp int numLights;
// out parameters to fragment shader: varyings
varying vec3 outNormal;
varying vec2 outTextCoord;
varying vec3 outViewVector;
varying vec3 outLightVector[MAX_LIGHTS];
void main(){
// Calculate view-space position coordinate
vec4 P = modelViewMatrix * vec4(position,1.0);
// Calculate the normal in view-space
outNormal = vec3(inversedTrasposedModelViewMatrix * vec4(normal ,0.0));
// Calculate the view vector in view-space space coordinate
outViewVector = -P.xyz;
// Assign the texture coordinate
outTextCoord = texCoord;
// Calculate clip-space position of each vertex
gl_Position = projectionMatrix * P;
// Calculate light vector for all light source
for (int indexLightVector = 0; indexLightVector < MAX_LIGHTS; indexLightVector++){
if (indexLightVector < numLights) {
/* Si no es ambiental: la unica luz que no lleva asociada vector */
if ((length(LightSource[indexLightVector].ambient) == 0.0) /* no ambiental */
&& (LightSource[indexLightVector].position.w != 0.0)){ /* no directional */
/* La luz es o point o spotLight */
outLightVector[indexLightVector] = vec3(modelViewMatrix*LightSource[indexLightVector].position) - P.xyz;
}
else if (length(LightSource[indexLightVector].ambient) == 0.0) { /* no ambiental */
/* La luz es directional: position es un vector,
* lo transformamos con inversedTransposedModelViewMatrix
* y lo negamos para que vaya desde el punto a la luz y no al revés
*/
outLightVector[indexLightVector] = - vec3(inversedTrasposedModelViewMatrix*LightSource[indexLightVector].position);
}
}
}
}
Фрагментный шейдер:
#pragma glsl
precision mediump float;
const int MAX_LIGHTS = 8;
struct LightSourceParameters {
vec3 ambient;
vec3 lightColor;
vec4 position;
float spotExponent;
float spotCutoff; // (range: [0.0,90.0], 180.0)
vec3 spotDirection;
float constantAttenuation;
float linearAttenuation;
float quadraticAttenuation;
};
uniform LightSourceParameters LightSource[MAX_LIGHTS];
struct MaterialParameters {
vec4 emission;
vec4 ambient;
vec4 diffuse;
sampler2D diffuseTexture;
int hasDiffuseTexture;
vec4 specular;
sampler2D specularTexture;
int hasSpecularTexture;
float shininess;
};
uniform MaterialParameters Material;
uniform lowp int numLights;
varying vec3 outNormal;
varying vec2 outTextCoord;
varying vec3 outViewVector;
varying vec3 outLightVector[MAX_LIGHTS];
/* Declaramos cabecera de funcion, necesaria para que GLSL no diga que la funcion no existe, al definirse despues de main */
vec4 computeLight(in MaterialParameters material, in LightSourceParameters lightSource, in vec3 normal, in vec2 textCoord, in vec3 lightVector, in vec3 halfVector);
void main(){
// Normalize the incoming vectors
vec3 normal = normalize(outNormal);
vec3 viewVector = normalize(outViewVector);
vec3 lightVector[MAX_LIGHTS];
vec3 halfVector[MAX_LIGHTS];
// normalize lightvector, compute half vectors and lights
vec4 totalColorLighting = vec4(0.0);
int indexComputeLights = 0;
for (indexComputeLights; indexComputeLights < MAX_LIGHTS; indexComputeLights++){
if (indexComputeLights < numLights) {
totalColorLighting = totalColorLighting + vec4(0.05);
if (length(LightSource[indexComputeLights].ambient) == 0.0 ){ /* no es ambiental, que no tienen vector */
lightVector[indexComputeLights] = normalize(outLightVector[indexComputeLights]);
}
if (length(LightSource[indexComputeLights].ambient) == 0.0 ){ /* no es ambiental, que no tienen half vector */
halfVector[indexComputeLights] = normalize(outLightVector[indexComputeLights] + outViewVector);
}
LightSourceParameters light = LightSource[indexComputeLights];
vec3 currentLightVector = lightVector[indexComputeLights];
vec3 currentHalfVector = halfVector[indexComputeLights];
/* Si la luz es ambiental, halfVector y lightVector son
* indefinidos para esa luz, pero da igual porque no son
* utilizados en el algoritmo que calcula las luces
*/
totalColorLighting = totalColorLighting + computeLight(Material, light, normal, outTextCoord, currentLightVector, currentHalfVector);
indexComputeLights = indexComputeLights + 1;
}
}
vec4 emission = Material.emission;
// vec4 emission = vec4(0.5);
// totalColorLighting = vec4(0.0);
// Compute emission material
if (length(emission) != 0.0) { /* El material tiene un termino emisivo, es decir, emite luz. Lo andimos al total de color calculado */
totalColorLighting = totalColorLighting + emission;
}
/* Devolvemos el color de fragmento calculado para almacenarlo en el framebuffer */
gl_FragColor = totalColorLighting;
//gl_FragColor = vec4(1.0);
}
vec4 computeLight(in MaterialParameters material, in LightSourceParameters lightSource,
in vec3 normal, in vec2 textCoord, in vec3 lightVector, in vec3 halfVector){
float attenuation = 1.0; // no attenuation
vec4 totalLightingColor = vec4(0.0); // no color
if (length(lightSource.ambient) > 0.0){ // es luz ambiente
totalLightingColor = vec4(lightSource.ambient, 1.0) * material.ambient;
}
else { // Is not ambiental light
if (lightSource.position.w == 0.0) { // es un vector, por lo tanto es una luz direccional
attenuation = 1.0; // no attenuation
}
else { // Is a point light or a spot light
float distanceToLight = length(lightVector);
attenuation = 1.0 / (lightSource.constantAttenuation +
(lightSource.linearAttenuation * distanceToLight) +
(lightSource.quadraticAttenuation * distanceToLight * distanceToLight));
if (lightSource.spotCutoff <= 90.0){ /* Is a spot light */
vec3 spotDirection = normalize(lightSource.spotDirection);
float clampedCosine = max(0.0, dot(-lightVector, spotDirection));
if (clampedCosine < cos(radians(lightSource.spotCutoff))){ /* outside the spotlight cone */
attenuation = 0.0; /* full attenuation */
}
else { /* inside the spotlight cone */
attenuation = attenuation * pow(clampedCosine, lightSource.spotExponent);
}
}
}
// Calculo de los terminos de color: diffuso y especular
vec4 diffuseMaterialTerm = vec4(0.0, 0.0, 0.0, 1.0); /* El canal difuso será opaco y negro hasta que se sobreescriban sus datos */
if (material.hasDiffuseTexture == 0) { /* El canal difuso no tiene textura */
diffuseMaterialTerm = material.diffuse;
}
else if (material.hasDiffuseTexture == 1){
diffuseMaterialTerm = texture2D(material.diffuseTexture, textCoord);
}
vec4 diffuseReflection = attenuation * vec4(lightSource.lightColor, 1.0) * diffuseMaterialTerm * max(0.0, dot(normal, lightVector));
vec4 specularReflection = vec4(0.0);
if (dot(normal, lightVector) < 0.0 ) { // light source in the wrong side
specularReflection = vec4(0.0);
}
else { // light source in the right side
float NdotHV = max(dot(normal, halfVector), 0.0); /* Normal-dot-halfvector */
vec4 specularMaterialTerm = vec4 (0.0, 0.0, 0.0, 1.0); /* El canal especular será opaco y negro hasta que se sobreescriban sus datos */
if (material.hasSpecularTexture == 0){
specularMaterialTerm = material.specular;
}
else if (material.hasSpecularTexture == 1){
specularMaterialTerm = texture2D(material.specularTexture, textCoord);
}
specularReflection = attenuation * pow(NdotHV, material.shininess) * vec4(lightSource.lightColor, 1.0) * specularMaterialTerm;
}
totalLightingColor = diffuseReflection + specularReflection;
}
return totalLightingColor;
}
Это код (Java), который я использую для загрузки этой униформы (numLights):
GLES20.glUseProgram(gl_Program.GetProgramID()); // GetProgramID() returns 3 in my case
int numLights = iLightList.size(); // the scene I want to render has 1 light, so this value is 1
String numLightsString = "numLights";
int gl_numLights_Uniform_Locator = gl_Program.GetUniformLocation(numLightsString); // is 83 in my program
GLES20.glUniform1i(gl_numLights_Uniform_Locator, numLights);
int error = GLES20.glGetError(); // error is always 0
Любая помощь будет очень признательна
РЕДАКТИРОВАТЬ 5:
Я нашел что-то очень странное. Если я прокомментирую этот код во фрагментном шейдере, юниформ numLights будет загружен с правильным значением:
// vec4 emission = Material.emission;
// if (length(emission) != 0.0) { /* El material tiene un termino emisivo, es decir, emite luz. Lo añadimos al total de color calculado */
// totalColorLighting = totalColorLighting + emission;
// }
Но если я это сделаю, я потеряю расчет части эмиссионного света... Есть идеи?