OpenGL.Shader: Realización de 11 sombras-Sombra de luz direccional

OpenGL.Shader: Realización de 11 sombras-Sombra de luz direccional

 

Siento que no he escrito un artículo de estudio durante mucho tiempo, y estoy principalmente ocupado con el trabajo y la vida. Después del Día Nacional, no queda mucho tiempo en 2019. Lucha por ti y tus seres queridos.

1. La teoría del mapeo de sombras

Las sombras son el resultado de la luz bloqueada; cuando la luz de una fuente de luz no puede alcanzar la superficie de un objeto debido a la obstrucción de otros objetos, entonces el objeto está en la sombra. Las sombras pueden hacer que la escena parezca mucho más real y permitir que el observador obtenga la relación de posición espacial entre los objetos.

Esta vez, el contenido debe usar la textura de profundidad de la sección anterior, y el portal está aquí. La textura de profundidad es un objeto de textura cuyo valor de profundidad es el contenido principal de almacenamiento. Su principio es: el ángulo de la fuente de luz apunta al espacio de la matriz de la fuente de luz del objetivo de observación (matriz de proyección * matriz de vista) . La prueba de profundidad se inicia en este espacio de fuente de luz y todos los objetos de observación se guardan. La situación de la oclusión. Según la situación de oclusión, realice el mapeo de sombras.

La idea detrás de Shadow Mapping es muy simple: usamos la posición de la luz como perspectiva para renderizar, todo lo que podemos ver estará iluminado y lo invisible debe estar en las sombras. Supongamos que hay un piso y una caja grande entre la fuente de luz y ella. Dado que la fuente de luz está mirando en la dirección de la luz, se puede ver la caja, pero parte del piso no es visible, esta parte debe estar en la sombra. (Todas las líneas azules aquí representan los fragmentos que la fuente de luz puede ver. Las líneas negras representan los fragmentos ocluidos)

 

Se acabó la aburrida parte teórica de aprendizaje Según la teoría anterior, no es difícil encontrar que la realización de la sombra es un algoritmo general, siempre que haya una fuente de luz + objeto = mapeo de sombras. A continuación, se toma el código de sombreado como ejemplo para comprender mejor la realización de las sombras.

#version 320 es
en posición vec3;
en vec3 normal;
en vec2 uv;

out VS_OUT {     vec3 FragPosWorldSpace;     vec3 Normal;     vec2 TexCoords;     vec4 FragPosLightSpace; } vs_out;




proyección uniforme mat4;
vista uniforme mat4;
modelo mat4 uniforme;
uniforme mat4 lightSpaceMatrix;

void main ()
{     gl_Position = proyección * vista * modelo * vec4 (posición, 1.0f);     vs_out.TexCoords = uv; // Las coordenadas de textura uv se pasan al sombreador de fragmentos para la interpolación     vs_out.Normal = transpose (inverse (mat3 (modelo))) * normal;     vs_out.FragPosWorldSpace = vec3 (modelo * vec4 (posición, 1.0));     vs_out.FragPosLightSpace = lightSpaceMatrix * vec4 (vs_out.FragPosWorldSpace, 1.0); }





El contenido del sombreador de vértices es relativamente fácil de entender, y la estructura VS_OUT se usa para integrar todas las salidas relevantes; el
vector normal necesita ser transformado por la matriz de vector normal (transponer la matriz inversa de la matriz del modelo);
FragPosWorldSpace = coordenadas de posición absoluta en el sistema de coordenadas mundial ,
Guarde el sombreador de fragmentos para usar; lightSpaceMatrix es el espacio de la matriz de la fuente de luz mencionado anteriormente, el espacio de la matriz de la fuente de luz * la coordenada de posición absoluta es la posición del vértice debajo del espacio de la fuente de luz;

A continuación, observe el punto clave: el sombreador de fragmentos.

#version 320 es
precisión mediump float;
uniform vec3 _lightColor;
sampler2D uniforme _texture;
sampler2D uniforme _shadowMap;
uniform vec3 _lightPos;
uniform vec3 _viewPos;

en VS_OUT {     vec3 FragPosWorldSpace;     vec3 Normal;     vec2 TexCoords;     vec4 FragPosLightSpace; } fs_in;




out vec4 fragColor;

float ShadowCalculation (vec4 fragPosLightSpace)
{     [...] } void main () {     vec3 color = texture (_texture, fs_in.TexCoords) .rgb;     vec3 normal = normalize (fs_in.Normal);     vec3 lightColor = _lightColor;     // circundante Ambient     vec3 ambient = 0.5 * color;     // reflexión difusa diffuse     vec3 lightDir = normalize (_lightPos-fs_in.FragPosWorldSpace);     float diff = max (dot (lightDir, normal), 0.0);     vec3 diffuse = diff * lightColor;     // Distorsión de sombra     // sesgo de flotación = max (0.01 * (1.0-dot (normal, lightDir)), 0.0005);     // Calcula la sombra     flotante shadow = ShadowCalculation (fs_in.FragPosLightSpace);

















    vec3 iluminación = (ambiente + (1.0 - sombra) * difusa) * color;
    fragColor = vec4 (iluminación, 1.0f);
}

El sombreador de fragmentos utiliza el modelo de iluminación Blinn-Phong para renderizar la escena. Luego calcule un valor de sombra, que es 1.0 cuando el fragmento está en la sombra y 0.0 cuando está fuera de la sombra. Entonces, el color difuso se multiplicará por este elemento de sombra. Dado que la sombra no será completamente negra (debido a la dispersión), eliminamos el componente ambiental de la multiplicación.

Para comprobar si un fragmento está en la sombra, primero convierta la posición del fragmento en el espacio de luz en las coordenadas del dispositivo estandarizadas del espacio de corte (en medios vernáculos: el espacio 3D se convierte de nuevo a las coordenadas 2D en la pantalla y la textura de profundidad Comparar). Cuando generamos una posición de vértice del espacio de recorte en gl_Position en el sombreador de vértices, OpenGL realiza automáticamente una división de perspectiva, que convierte el rango de coordenadas del espacio de recorte -w ah en -1 a 1, lo que requiere x, y, z El elemento se divide por el elemento w del vector. Dado que el FragPosLightSpace del espacio de recorte no se pasará al sombreador de píxeles a través de gl_Position, debemos hacer la división de perspectiva nosotros mismos:

// Realizar la división de perspectiva
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;

Los componentes xyz de los projCoords anteriores son todos [-1,1] (lo siguiente señalará que esto solo es cierto para puntos como el plano lejano), y para comparar con la profundidad del mapa de profundidad, el componente z debe transformarse a [0,1] ; Para ser las coordenadas muestreadas del mapa de profundidad, el componente xy también debe transformarse a [0,1]. Así que todo el vector projCoords necesita ser transformado al rango [0,1].

projCoords = projCoords * 0.5 + 0.5;

Con estas coordenadas de proyección, podemos muestrear los resultados de 0 a 1 de la textura de profundidad, y las coordenadas de projCoords de la primera etapa de renderizado corresponden directamente a las coordenadas NDC transformadas. Obtendremos la profundidad más cercana bajo el campo de visión de la posición de la luz: 

flotar la profundidad más cercana = textura (shadowMap, projCoords.xy) .r;

Para obtener la profundidad actual del fragmento, simplemente obtenemos la coordenada z del vector de proyección, que es igual a la profundidad del fragmento desde la perspectiva de la luz. 

float currentDepth = projCoords.z; 

La comparación real es simplemente para verificar si currentDepth es mayor que closetDepth. Si lo es, entonces el fragmento está en la vista posterior ocluida. 

flotante sombra = profundidad actual> profundidad más cercana? 1,0: 0,0; 

La función completa shadowCalculation se ve así:

float ShadowCalculation(vec4 fragPosLightSpace)
{
    // 执行透视除法
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    // 变换到[0,1]的范围
    projCoords = projCoords * 0.5 + 0.5;
    // 取得最近点的深度(使用[0,1]范围下的fragPosLight当坐标)
    float closestDepth = texture(shadowMap, projCoords.xy).r; 
    // 取得当前片元在光源视角下的深度
    float currentDepth = projCoords.z;
    // 检查当前片元是否在阴影中
    float shadow = currentDepth > closestDepth  ? 1.0 : 0.0;
    return shadow;
}

Dos, realización de sombras

La parte teórica clave del estudio ha terminado, el siguiente paso es la operación real. Planeo hacer una fuente de luz de punto fijo, iluminar la hierba verde y el cubo originales y presentar un efecto de sombra. La posición de la fuente de luz se simula mediante un pequeño cubo blanco.

void ShadowFBORender::surfaceCreated(ANativeWindow *window)
{
    if (mEglCore == NULL) {
        mEglCore = new EglCore(NULL, FLAG_TRY_GLES3);
    }
    mWindowSurface = new WindowSurface(mEglCore, window, true);
    mWindowSurface->makeCurrent();

    char res_name[250]={0};
    sprintf(res_name, "%s%s", res_path, "land.jpg");
    GLuint land_texture_id = TextureHelper::createTextureFromImage(res_name);
    sprintf(res_name, "%s%s", res_path, "test.jpg");
    GLuint texture_cube_id = TextureHelper::createTextureFromImage(res_name);
    // 带阴影效果的正方体
    cubeShadow.init(CELL::float3(1,1,1));
    cubeShadow.setSurfaceTexture(texture_cube_id);
    // 带阴影效果的草地板
    landShadow.init(10, -1);
    landShadow.setSurfaceTexture(land_texture_id);
    // 实际的光源位置
    mLightPosition = CELL::real3(5, 5, 2);
    // 模拟光源位置的小正方体
    lightPositionCube.init(CELL::real3(0.15f,0.15f,0.15f), 0);
    lightPositionCube.mModelMatrix.translate(mLightPosition);
}

A continuación, implemente el proceso de dibujo y renderizado.

void ShadowFBORender::renderOnDraw(double elpasedInMilliSec)
{
    mWindowSurface->makeCurrent();

    matrix4 cProj(mCamera3D.getProject());
    matrix4 cView(mCamera3D.getView());
    mLightProjectionMatrix = CELL::perspective(45.0f, (float)mViewWidth/(float)mViewHeight, 0.1f, 30.0f);
    mLightViewMatrix       = CELL::lookAt(mLightPosition, CELL::real3(0,0,0), CELL::real3(0,1.0,0));
    // Note.绘制深度纹理
    renderDepthFBO();
    
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
    glViewport(0,0, mViewWidth, mViewHeight);
    // 绘制模拟光源位置的小正方体
    lightPositionCube.render(mCamera3D);
    // 绘制带阴影效果的地板
    landShadow.setShadowMap(depthFBO.getDepthTexId());
    landShadow.render(cProj,cView, mLightPosition, mLightProjectionMatrix, mLightViewMatrix);
    // 绘制带阴影效果的正方体
    cubeShadow.setShadowMap(depthFBO.getDepthTexId());
    cubeShadow.render(cProj,cView, mLightPosition, mLightProjectionMatrix, mLightViewMatrix);

    mWindowSurface->swapBuffers();
}

void ShadowFBORender::renderDepthFBO()
{
    depthFBO.begin();
    {
        glEnable(GL_DEPTH_TEST);
        glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
        //glEnable(GL_CULL_FACE);
        //glCullFace(GL_FRONT);
        landShadow.render(mLightProjectionMatrix,mLightViewMatrix,
                          mLightPosition,
                          mLightProjectionMatrix,mLightViewMatrix);
        cubeShadow.render(mLightProjectionMatrix,mLightViewMatrix,
                          mLightPosition,
                          mLightProjectionMatrix,mLightViewMatrix);
        //glCullFace(GL_BACK);
        //glDisable(GL_CULL_FACE);
    }
    depthFBO.end();
}

La diferencia con el anterior es el método de dibujo del objeto LandShadow / CubeShadow. Anteriormente, se pasaba el objeto Camera3D. En el método de textura de profundidad de renderizado renderDepthFBO, los parámetros entrantes también son diferentes. Es principalmente el valor de profundidad en el espacio de luz requerido por la textura de profundidad. En el renderizado real, es el efecto del espacio de visión de la cámara. Tome LandShadow como ejemplo para ver el código específico.

class LandShadow {
public:
    struct V3N3T2 {
        float x, y, z; //位置坐标
        float nx, ny, nz; //法向量
        float u,v; //纹理坐标
    };
public:
    V3N3T2                  _data[6];
    CELL::matrix4           _modelMatrix;
    GLuint                  _texId;
    GLuint                  _ShadowMapId;
    IlluminateWithShadow    sprogram;

    void        setShadowMap(GLuint texId) {
        _ShadowMapId = texId;
    }
    void        setSurfaceTexture(GLuint texId) {
        _texId = texId;
    }
    void        init(const float size, const float y_pos)
    {
        float   gSizeX = 10;
        float   gSizeZ = 10;
        V3N3T2 verts[] =
        {
            {-gSizeX, y_pos, -gSizeZ, 0,1,0,  0.0f, 0.0f}, // left far
            { gSizeX, y_pos, -gSizeZ, 0,1,0,  size, 0.0f}, // right far
            { gSizeX, y_pos,  gSizeZ, 0,1,0,  size, size}, // right near
            {-gSizeX, y_pos, -gSizeZ, 0,1,0,  0.0f, 0.0f}, // left far
            { gSizeX, y_pos,  gSizeZ, 0,1,0,  size, size}, // right near
            {-gSizeX, y_pos,  gSizeZ, 0,1,0,  0.0f, size}  // left near
        };
        memcpy(_data, verts, sizeof(verts));
        _modelMatrix.identify();
        sprogram.initialize();
    }

    void        render(matrix4 currentProjectionMatrix, matrix4 currentViewMatrix,
                       real3& lightPos,
                       matrix4 lightProjectionMatrix, matrix4 lightViewMatrix)
    {
        sprogram.begin();
        // 加载材质纹理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, _texId);
        glUniform1i(sprogram._texture, 0);
        // 加载阴影深度测试的纹理
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D,  _ShadowMapId);
        glUniform1i(sprogram._shadowMap, 1);
        // 用于对象顶点坐标的空间转换
        glUniformMatrix4fv(sprogram._projection, 1, GL_FALSE, currentProjectionMatrix.data());
        glUniformMatrix4fv(sprogram._view, 1, GL_FALSE, currentViewMatrix.data());
        glUniformMatrix4fv(sprogram._model, 1, GL_FALSE, _modelMatrix.data());
        
        glUniform3f(sprogram._lightColor, 1.0f, 1.0f, 1.0f);
        glUniform3f(sprogram._lightPos, lightPos.x, lightPos.y, lightPos.z);
        // 光源空间矩阵
        matrix4 lightSpaceMatrix = lightProjectionMatrix * lightViewMatrix;
        glUniformMatrix4fv(sprogram._lightSpaceMatrix, 1, GL_FALSE, lightSpaceMatrix.data());
        // 绘制
        glVertexAttribPointer(static_cast<GLuint>(sprogram._position), 3, GL_FLOAT, GL_FALSE,
                              sizeof(LandShadow::V3N3T2), &_data[0].x);
        glVertexAttribPointer(static_cast<GLuint>(sprogram._normal),   3, GL_FLOAT, GL_FALSE,
                              sizeof(LandShadow::V3N3T2), &_data[0].nx);
        glVertexAttribPointer(static_cast<GLuint>(sprogram._uv),       2, GL_FLOAT, GL_FALSE,
                              sizeof(LandShadow::V3N3T2), &_data[0].u);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        sprogram.end();
    }
};

El primer parámetro y el segundo parámetro se utilizan para representar la posición del vértice específico del objeto, el tercer parámetro es la posición de la fuente de luz;

El cuarto parámetro y el quinto parámetro se utilizan para calcular la posición del vértice del objeto de la matriz espacial de la fuente de luz para facilitar el juicio de sombras del fragmento donde se encuentra el vértice.

El código anterior, si no hay una gran diferencia, el efecto de ejecución probablemente sea así:

Este fenómeno se debe a la existencia de pruebas de distorsión de las sombras muy graves y pruebas de oclusión de sombras redundantes. ¿Cómo lidiar con ello? ¡Escuche la descomposición la próxima vez!

Supongo que te gusta

Origin blog.csdn.net/a360940265a/article/details/102516439
Recomendado
Clasificación