【问题标题】:Creating a rectangular light source in OpenGL?在OpenGL中创建一个矩形光源?
【发布时间】:2014-04-09 04:22:12
【问题描述】:

我正在尝试在 OpenGL 中为一个应用程序创建一个矩形、锐利的光源。我的想法是创建一个聚光灯并以某种方式将阴影的形状遮盖成一个矩形,当然遮罩必须通过相机不可见。当我试图实现这个想法时,事实证明 OpenGL 只会跳过在相机外渲染对象,尽管相机外的光源仍然有效。这使我无法创建我想要的效果,我想知道你们中是否有人遇到过类似的问题。

为了让我的问题更具体,请考虑以下我的问题:

0,0,5 处的聚光灯

目标对象位于 0,0,0

在 0,0,3 处遮罩对象(平行于 x 轴的简单四边形)。

当相机在 0,0,4 时,光线穿过遮罩物体并在目标物体上留下一个矩形(这是我想要的),但我也可以看到遮罩物体!(而我需要遮罩对象不可见)

当我将相机移近目标对象时,例如 0,0,2。蒙版对象位于相机后面,因此不可见。但是,由于它是不可见的,因此OpenGL停止了渲染,因此遮罩对象对目标对象没有任何影响,并且阴影仍然是圆形的!

【问题讨论】:

  • OpenGL 只有点光源。您可能想考虑制作一个由平面上带有矩形孔的点光,并使用一些阴影技术(阴影贴图、模板阴影等)。或者,如果那是唯一的光并且您不喜欢制作阴影 - 为矩形光编写自定义光照着色器。
  • 您好 Korm Stem,非常感谢您的及时回复。我确实需要其他灯光,而且我也很难用默认的 OpenGL 聚光灯生成一个锐利的光源。也许我确实需要为此编写一个着色器。再次感谢!
  • @KromStern:实际上现代 OpenGL 根本没有光源。它有一个可编程的着色器管道,光源是任何你想出的可以在着色器中实现的照明模型。

标签: opengl


【解决方案1】:

我的猜测是从聚光灯开始,但分离角度计算: * 将L向量投影到YZ平面上,计算X轴上的角度 * 将L向量投影到XZ平面上,计算Y轴上的角度

一个非常天真的实现可能是(GLSL):

varying vec3 v_V; // World-space position
varying vec3 v_N; // World-space normal

uniform float time; // global time in seconds since shaderprogram link
uniform vec2 uSpotSize; // Spot size, on X and Y axes

vec3 lp = vec3(0.0, 0.0, 7.0 + cos(time) * 5.0); // Light world-space position
vec3 lz = vec3(0.0, 0.0, -1.0); // Light direction (Z vector)

// Light radius (for attenuation calculation)
float lr = 3.0;

void main()
{
    // Calculate L, the vector from model surface to light
    vec3 L = lp - v_V;

    // Project L on the YZ / XZ plane
    vec3 LX = normalize(vec3(L.x, 0.0, L.z));
    vec3 LY = normalize(vec3(0.0, L.y, L.z));

    // Calculate the angle on X and Y axis using projected vectors just above
    float ax = dot(LX, -lz);
    float ay = dot(LY, -lz);

    // Light attenuation
    float d = distance(lp, v_V);
    float attenuation = 1.0 / (1.0 + (2.0/lr)*d + (1.0/(lr*lr))*d*d);

    float shaded = max(0.0, dot(v_N, L)) * attenuation;

    if(ax > cos(uSpotSize.x) && ay > cos(uSpotSize.y))
        gl_FragColor = vec4(shaded); // Inside the light influence zone, light it up !
    else
        gl_FragColor = vec4(0.1); // Outside the light influence zone.
}

再一次,这很幼稚。例如,X/Y 投影是在世界空间中完成的。如果您希望能够旋转灯光矩形,您可能需要引入一个指向灯光右侧的向量。 因此,您将能够在灯光的坐标系中获取片段坐标,然后您可以决定是否对片段进行着色。

【讨论】:

    【解决方案2】:

    一种解决方案可能是调整用于projective texture lookups 的计算来模拟矩形光源。您没有指定您使用的是哪个 OpenGL 版本,但投影纹理查找甚至可以使用固定功能管道实现 - 尽管可以说它们在着色器中更容易做到。

    当然,这不会模拟一个矩形区域光源,只是一个被限制在一个矩形区域内的点光源。

    使用这种方法,您必须为光源指定视图和投影矩阵;其中视图矩阵本质上是由具有光位置及其方向的“查看”生成的;投影矩阵使用您想要的水平和垂直“视野”对透视投影进行编码。

    如果你只想要一个矩形区域,你甚至不需要纹理;一个简单的顶点/片段着色器对可能如下所示: (顶点着色器基本上将位置转换为灯光的剪辑空间,片段着色器执行剪辑并在片段位于灯光截锥体内时计算朗伯着色)

    #version 330 core
    
    layout ( location = 0 ) in vec3 vertexPosition;
    layout ( location = 1 ) in vec3 vertexNormal;
    layout ( location = 3 ) in vec3 vertexDiffuse;
    
    uniform mat4 modelTf;
    uniform mat3 normalTf;
    uniform mat4 viewTf;                    // view matrix for render camera
    uniform mat4 projectiveTf;              // projection matrix for render camera
    uniform mat4 viewTf_lightCam;           // view matrix of light source
    uniform mat4 projectiveTf_lightCam;     // projective matrix of light source
    
    uniform vec4 lightPosition_worldSpace;
    
    out vec3 diffuseColor;
    out vec3 normal_worldSpace;
    out vec3 toLight_worldSpace;
    out vec4 position_lightClipSpace;
    
    void main()
    {
        diffuseColor = vertexDiffuse;
        vec4 vertexPosition_worldSpace = modelTf * vec4( vertexPosition, 1.0 );
        normal_worldSpace = normalTf * vertexNormal;
        toLight_worldSpace = normalize( lightPosition_worldSpace - vertexPosition_worldSpace ).xyz;
        position_lightClipSpace = projectiveTf_lightCam * viewTf_lightCam * vertexPosition_worldSpace;
        gl_Position = projectiveTf * viewTf * vertexPosition_worldSpace;
    }
    
    #version 330 core
    
    layout ( location=0 ) out vec4 fragColor;
    
    in vec3 diffuseColor;
    in vec3 normal_worldSpace;
    in vec3 toLight_worldSpace;
    in vec4 position_lightClipSpace;
    
    uniform vec3 ambientLight;
    
    void main()
    {
        // clipping against the light frustum
        bool isInsideX = ( position_lightClipSpace.x <= position_lightClipSpace.w && position_lightClipSpace.x >= -position_lightClipSpace.w );
        bool isInsideY = ( position_lightClipSpace.y <= position_lightClipSpace.w && position_lightClipSpace.y >= -position_lightClipSpace.w );
        bool isInsideZ = ( position_lightClipSpace.z <= position_lightClipSpace.w && position_lightClipSpace.z >= -position_lightClipSpace.w );
        bool isInside = isInsideX && isInsideY && isInsideZ;
    
        vec3 N = normalize( normal_worldSpace );
        vec3 L = normalize( toLight_worldSpace );
        vec3 lightColor = isInside ? max( dot( N, L ), 0.0 ) * vec3( 0.99, 0.66, 0.33 ) : vec3( 0.0 );
    
        fragColor = vec4( clamp( ( ambientLight + lightColor ) * diffuseColor, vec3( 0.0 ), vec3( 1.0 ) ), 1.0 );
    }
    

    【讨论】:

      【解决方案3】:

      有很多关于这方面的好论文,Brian Karis 在 2013 年(关于 UE4)在这里写过:

      https://de45xmedrsdbp.cloudfront.net/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf

      最近,Michal Drobot 写了一篇关于 GPU Pro 5 中区域光的文章。

      如果您使用的是金属度工作流程,您还可以将粗糙度提高为区域照明的近似值,这是 Tri-Ace 引入的一种技术:

      http://www.fxguide.com/featured/game-environments-parta-remember-me-rendering/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多