如何让OpenGL中多盏2D点光源重叠时显示效果自然?
多盏点光源重叠效果修复方案
问题根源
你当前的实现是每个光源单独渲染一次场景,并通过min()函数限制最终颜色不超过原纹理颜色。这种处理会导致多光源重叠时,后渲染的光源会把前一个光源提亮的颜色重新压回原纹理色,无法实现光照的累加效果,自然重叠区域显示异常。
正确实现流程(多光源累加)
采用帧缓冲(FBO)分层渲染的方式,将场景渲染和光照计算分离,最后合成结果:
- 先把整个场景渲染到一个帧缓冲,得到不带光照的基础场景纹理。
- 创建一个用于累积光照的帧缓冲,初始填充环境光颜色。
- 对每个点光源,计算其光照贡献并通过加法混合累加到光照缓冲中。
- 最后将场景纹理和光照纹理相乘,得到带多光源效果的最终画面。
对应着色器代码修改
1. 场景渲染着色器(生成无光照的基础场景)
顶点着色器
#version 400 core in vec2 position; in vec2 texCoords; out vec2 passTexCoords; uniform mat4 uProjection; uniform mat4 uView; void main() { gl_Position = uProjection * uView * vec4(position, 1.0, 1.0); passTexCoords = texCoords; }
片段着色器
#version 400 core in vec2 passTexCoords; uniform sampler2D textureSampler; uniform vec3 fontColor; void main() { vec4 tex_color = texture(textureSampler, passTexCoords); if (tex_color.a < 1.0) discard; gl_FragColor = vec4(tex_color.rgb * fontColor, 1.0); }
2. 光源计算着色器(累加单光源贡献)
顶点着色器(渲染全屏四边形)
#version 400 core in vec2 position; out vec2 passScreenCoords; uniform float aspect; void main() { gl_Position = vec4(position, 0.0, 1.0); // 转换为适配宽高比的屏幕坐标 passScreenCoords = vec2(position.x * aspect, position.y); }
片段着色器
#version 400 core in vec2 passScreenCoords; uniform vec3 lightColor; uniform float lightIntensity; uniform vec2 lightPosition; uniform mat4 uProjection; uniform mat4 uView; uniform float aspect; void main() { // 将世界空间光源位置转换为NDC空间坐标 vec4 cameraLightPos = uProjection * uView * vec4(lightPosition, 1.0, 1.0); vec2 finalLightPos = vec2(cameraLightPos.x * aspect, cameraLightPos.y); float lightDistance = distance(finalLightPos, passScreenCoords); float diffuse = 0.0; // 线性衰减(可替换为更真实的平方反比衰减) if (lightDistance <= lightIntensity) { diffuse = 1.0 - (lightDistance / lightIntensity); // 平方反比衰减示例:diffuse = clamp(lightIntensity / (lightDistance * lightDistance), 0.0, 1.0); } // 输出该光源的贡献,后续通过加法混合累加 gl_FragColor = vec4(lightColor * diffuse, 1.0); }
3. 最终合成着色器(场景+光照合并)
顶点着色器(全屏四边形)
#version 400 core in vec2 position; out vec2 passTexCoords; void main() { gl_Position = vec4(position, 0.0, 1.0); // 将NDC坐标转换为纹理采样坐标(0~1范围) passTexCoords = (position + 1.0) / 2.0; }
片段着色器
#version 400 core in vec2 passTexCoords; uniform sampler2D sceneSampler; uniform sampler2D lightSampler; uniform vec3 ambientColor; void main() { vec3 sceneColor = texture(sceneSampler, passTexCoords).rgb; // 环境光+累积的光源贡献 vec3 totalLight = texture(lightSampler, passTexCoords).rgb + ambientColor; // 限制颜色值不超过1.0,避免过曝 vec3 finalColor = min(sceneColor * totalLight, vec3(1.0)); gl_FragColor = vec4(finalColor, 1.0); }
LWJGL端渲染流程关键代码示例
// 初始化FBO(场景缓冲、光照缓冲) int sceneFBO = glGenFramebuffers(); glBindFramebuffer(GL_FRAMEBUFFER, sceneFBO); int sceneTexture = glGenTextures(); // ... 配置纹理参数(RGBA、过滤方式等) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sceneTexture, 0); int lightFBO = glGenFramebuffers(); glBindFramebuffer(GL_FRAMEBUFFER, lightFBO); int lightTexture = glGenTextures(); // ... 配置纹理参数 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, lightTexture, 0); // 渲染流程 // 1. 渲染场景到sceneFBO glBindFramebuffer(GL_FRAMEBUFFER, sceneFBO); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // ... 绑定场景着色器,绘制所有场景元素 // 2. 初始化光照缓冲为环境光 glBindFramebuffer(GL_FRAMEBUFFER, lightFBO); glClearColor(ambientColor.x, ambientColor.y, ambientColor.z, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // 3. 累加每个光源的贡献 glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); // 加法混合模式 // ... 绑定光源着色器 for (Light light : lights) { // 设置光源 uniforms(位置、颜色、强度等) glUniform2f(lightLocation, light.getX(), light.getY()); glUniform3f(lightColorLocation, light.getR(), light.getG(), light.getB()); glUniform1f(lightIntensityLocation, light.getIntensity()); // 绘制全屏四边形 glBindVertexArray(fullScreenVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glDisable(GL_BLEND); // 4. 合成场景与光照,输出到屏幕 glBindFramebuffer(GL_FRAMEBUFFER, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // ... 绑定合成着色器 glUniform1i(sceneSamplerLocation, 0); glUniform1i(lightSamplerLocation, 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, sceneTexture); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, lightTexture); // 绘制全屏四边形 glBindVertexArray(fullScreenVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
内容的提问来源于stack exchange,提问作者Philliam
相关产品推荐
相关产品推荐

