Uniform int всегда равен нулю в фрагментном шейдере

Я делаю свой собственный движок для своей игры, и у меня было много проблем с шейдерами. Одним из самых раздражающих является то, что я не могу вычислить источники света, потому что условие во фрагментном шейдере никогда не выполняется, поэтому никогда не суммируются цвета различных материалов/светов. Кусок кода, в котором я обнаружил проблему, таков:

// 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;
 // }

Но если я это сделаю, я потеряю расчет части эмиссионного света... Есть идеи?


person manu    schedule 28.01.2014    source источник


Ответы (2)


Похоже, что GLES20.glUniform1i следует использовать, поскольку GLES20.glUniform1iv было бы для uniform int numLights[1];. Я уверен, что вы уже это сделали, GLES20.glUniform1i должно стоять после GLES20.glUseProgram и до того, как что-либо рисовать.

83 становится довольно высоким для местоположения, хотя и не безосновательным. Я мало что знаю о спецификациях Android, но, безусловно, может не хватить унифицированных мест и памяти. Возможно, попробуйте упростить шейдер (протестируйте с меньшим количеством источников света) или поместите униформу выше.

[EDIT] Чтобы продолжить эту идею, компиляторы GLSL могут быть очень неинтуитивными, когда речь идет о циклах и функциях. Также, когда ветвление начинает становиться довольно большим. Возможно, вы просто столкнулись с какой-то странной ошибкой компилятора GLSL. Вы можете попробовать развернуть цикл вручную, добавление indexComputeLights < MAX_LIGHTS && indexComputeLights < numLights в условие цикла for должно привести к вложению сгенерированных условий (по крайней мере, на моей карте nvidia с текущими драйверами). Может быть, заменить дорогостоящий вызов computeLight на константу только для тестирования. Я сталкивался со случаями, когда сгенерированный код кажется слишком большим, и я получаю недопустимый результат, а не ошибку компиляции.

Влияет ли lowp на возможность установки униформы?

Другой способ получить желаемый результат, который также может быть более быстрым, — это скомпилировать шейдер для каждого количества источников света. Это можно легко сделать, вставив #define NUM_LIGHTS x вверху исходного кода.

person jozxyqk    schedule 29.01.2014
comment
Я не знаю, влияет ли lowp, но не похоже, потому что он также дает сбой, если я меняю точность в обоих шейдерах. Я тестировал с меньшим количеством источников света, используя константу MAX_LIGHTS с более низким значением, но безуспешно. Я не знаю, является ли 83 необоснованно высоким числом юниформ, но, по крайней мере, в моем opengl 3.0 note 3 (я использую GLES20 API) максимальное количество юниформ в каждом шейдере составляет минимум 230, максимум 250 (более 400). в сумме я думаю также, что измеряется в размере типа данных vec4, то есть не менее 400*4 типа данных int). Я также могу подтвердить, что в моем коде GLES20.glUseProgram находится перед вызовом glUniform1i. Спасибо. - person manu; 31.01.2014
comment
К сожалению, не было никакой разницы в том, чтобы изменить calculateLight на константу. Я не понимаю, хотите ли вы, чтобы я сделал, чтобы развернуть цикл. Я сделал копирование/вставку внутри цикла 8 раз (число MAX_LIGHTS) и результат тоже не удался. Большое спасибо за Вашу помощь. - person manu; 02.02.2014

Я сталкиваюсь с идентичной проблемой, т.е. передача intuniform в вершинный шейдер приводит к непредсказуемому поведению НА НЕКОТОРОМ ОБОРУДОВАНИИ: он отлично работает на эмуляторе Android и на моем планшете Kindle Fire 2-го поколения, но это сбой (сбой телефона!) на Galaxy S III. Я обнаружил, что телефон зависает, потому что в вершинном шейдере цикл

for(int i=0; i<u_Sinks; i++)

где u_Sinks — это uniform int, кажется бесконечным. Но я делаю передачу 1 в u_Sinks следующим образом:

GLES20.glUniform1i( mSinksH  , 1);

и, конечно же, я убедился, что mSinksH получает допустимое значение>=0:

mSinksH          = GLES20.glGetUniformLocation(mProgramH, "u_Sinks");    
Log.d("distorted", "mSinksH="+mSinksH);

и журнал выводит mSinksH=4, что мне кажется правильным.

person Utumno    schedule 27.06.2014