渲染中实现阴影的各种方式

渲染中实现阴影的各种方式

在渲染中实现阴影的方式有多种,具体取决于所使用的渲染引擎和技术。以下是一些常见的实现阴影的方法:

平面投影 (Planar Shadow):Planar Shadow 平面投影(假阴影),低开销的阴影实现方式 - JeasonBoy - 博客园 (cnblogs.com)

开销最低的方式,思路就是根据光的方向将模型拉伸/压缩后画个黑影在地上,缺点是只能投在平面上,不能投射到其他物体上,斜面上。

阴影贴图 (Shadow Mapping):

阴影贴图是最常见的实时阴影技术之一。其基本思想是从光源的视角渲染场景,生成一张深度贴图,然后在实际渲染过程中使用这张深度贴图来判断像素是否在阴影中。

模板阴影:

TODO:一种过时的阴影技术,适用于简单场景

万向阴影贴图 (Omnidirectional Shadow Mapping):点阴影 - LearnOpenGL CN (learnopengl-cn.github.io)

上面的阴影贴图只能绘制一个方向的阴影,这是改进版。比如一个点光源在中心,那么它周围的物体都能向各个方向投影,性能消耗也比较高。

阴影体积 (Shadow Volumes):

阴影体积是一种精确的阴影生成方法。它通过将物体的几何体向光源方向投影,形成一个体积,然后在渲染过程中检测哪些像素在这个体积内,从而确定阴影。

屏幕空间阴影 (Screen Space Shadows):

屏幕空间阴影技术在屏幕空间中计算阴影,而不是在世界空间中。这种方法可以减少计算量,适合实时渲染。

软阴影 (Soft Shadows):

软阴影模拟的是现实中的阴影边缘柔和效果。可以通过多次采样 (PCF, Percentage Closer Filtering) 或者其他方法 (如 Variance Shadow Mapping) 来实现。

环境光遮蔽 (Ambient Occlusion):

虽然环境光遮蔽并不是传统意义上的阴影,但它可以增加场景的深度感。屏幕空间环境光遮蔽 (SSAO) 是一种常见的实时环境光遮蔽技术。

光线追踪 (Ray Tracing):

光线追踪是一种物理上更准确的渲染技术,它通过追踪光线的路径来生成逼真的阴影。虽然计算量较大,但现代GPU的硬件加速使其在实时渲染中逐渐变得可行。

阴影贴图

阴影贴图(Shadow Mapping)是一种常用的实时渲染技术,用于生成和计算场景中的阴影。其基本原理是通过从光源的角度生成深度信息,然后在实际渲染过程中使用这些深度信息来判断哪些像素在阴影中。下面详细讲解阴影贴图的实现步骤和技术细节。

1. 从光源角度渲染深度贴图

首先,从光源的角度渲染整个场景,并生成一张深度贴图(Depth Map)。这张贴图记录了从光源到每个物体表面的深度值。

步骤:

设置光源视图和投影矩阵:

根据光源的位置和方向,设置光源的视图矩阵和投影矩阵(通常是正交投影用于方向光,透视投影用于点光源和聚光灯)。

glm::mat4 lightView = glm::lookAt(lightPos, lightTarget, upVector);

glm::mat4 lightProjection = glm::ortho(left, right, bottom, top, near, far);

glm::mat4 lightSpaceMatrix = lightProjection * lightView;

渲染深度信息:

使用光源的视图和投影矩阵渲染场景,但只记录深度值,而不渲染颜色。

glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);

glClear(GL_DEPTH_BUFFER_BIT);

// Render scene to depth map

renderScene(lightSpaceMatrix);

glBindFramebuffer(GL_FRAMEBUFFER, 0);

2. 渲染实际场景并应用阴影贴图

在从相机视角渲染实际场景时,需要将光源的深度信息与当前像素的深度信息进行比较,以确定该像素是否在阴影中。

步骤:

计算当前像素在光源视图中的位置:

将当前像素的世界坐标转换到光源的视图空间。

vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPos, 1.0);

深度比较:

使用深度贴图中的深度值与当前像素的深度值进行比较。如果当前像素的深度大于深度贴图中的值,则该像素在阴影中。

float shadow = currentDepth > closestDepth + bias ? 1.0 : 0.0;

应用阴影因子:

使用阴影因子调整光照强度,生成阴影效果。

vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular));

3. 处理阴影伪影

阴影贴图方法中常见的伪影包括锯齿状阴影和彼得潘效应(Peter Panning)。可以通过以下技术来处理这些问题:

深度偏差(Depth Bias):

在进行深度比较时加入一个小的偏差,防止自阴影(Self-Shadowing)问题。

float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);

float shadow = currentDepth > closestDepth + bias ? 1.0 : 0.0;

百分比更接近过滤(Percentage Closer Filtering, PCF):

对深度贴图进行多次采样,并平均这些采样结果来生成更平滑的阴影边缘。

float shadow = 0.0;

vec2 texelSize = 1.0 / textureSize(shadowMap, 0);

for(int x = -1; x <= 1; ++x)

{

for(int y = -1; y <= 1; ++y)

{

float pcfDepth = texture(shadowMap, fragPosLightSpace.xy + vec2(x, y) * texelSize).r;

shadow += currentDepth > pcfDepth ? 1.0 : 0.0;

}

}

shadow /= 9.0;

4. 代码示例

以下是一个简化的OpenGL代码示例,展示了阴影贴图的实现步骤:

顶点着色器(Vertex Shader)

#version 330 core

layout (location = 0) in vec3 aPos;

layout (location = 1) in vec3 aNormal;

uniform mat4 model;

uniform mat4 view;

uniform mat4 projection;

uniform mat4 lightSpaceMatrix;

out vec4 FragPosLightSpace;

out vec3 FragPos;

out vec3 Normal;

void main()

{

FragPos = vec3(model * vec4(aPos, 1.0));

Normal = mat3(transpose(inverse(model))) * aNormal;

FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0);

gl_Position = projection * view * vec4(FragPos, 1.0);

}

片段着色器(Fragment Shader)

#version 330 core

out vec4 FragColor;

in vec4 FragPosLightSpace;

in vec3 FragPos;

in vec3 Normal;

uniform sampler2D shadowMap;

uniform vec3 lightPos;

uniform vec3 viewPos;

float ShadowCalculation(vec4 fragPosLightSpace)

{

// Perform perspective divide

vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;

// Transform to [0,1] range

projCoords = projCoords * 0.5 + 0.5;

// Get closest depth value from light's perspective (using [0,1] range fragment coords)

float closestDepth = texture(shadowMap, projCoords.xy).r;

// Get depth of current fragment from light's perspective

float currentDepth = projCoords.z;

// Check whether current frag pos is in shadow

float bias = max(0.05 * (1.0 - dot(Normal, lightDir)), 0.005);

float shadow = currentDepth > closestDepth + bias ? 1.0 : 0.0;

return shadow;

}

void main()

{

vec3 color = vec3(1.0); // Assume object color is white

vec3 lightColor = vec3(1.0);

vec3 ambient = 0.15 * lightColor;

// Diffuse shading

vec3 norm = normalize(Normal);

vec3 lightDir = normalize(lightPos - FragPos);

float diff = max(dot(norm, lightDir), 0.0);

vec3 diffuse = diff * lightColor;

// Specular shading

vec3 viewDir = normalize(viewPos - FragPos);

vec3 reflectDir = reflect(-lightDir, norm);

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);

vec3 specular = spec * lightColor;

// Calculate shadow

float shadow = ShadowCalculation(FragPosLightSpace);

vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;

FragColor = vec4(lighting, 1.0);

}

通过上述步骤和技术细节,可以实现基本的阴影贴图,并在实际应用中进一步优化和调整,以获得更好的视觉效果。

软阴影

软阴影(Soft Shadows)是指阴影的边缘是平滑的,而不是硬边的。这种阴影更接近于现实生活中的阴影,因为在现实世界中,光源通常不是点光源,而是具有一定面积的面光源或体积光源,导致阴影边缘出现渐变。

软阴影的生成

生成软阴影的方法有很多,包括:

面积光源采样(Area Light Sampling): 通过对光源的多个点进行采样,生成阴影的柔和边缘。

阴影贴图的模糊(Shadow Map Blurring): 对阴影贴图进行模糊处理,使得阴影边缘变得柔和。

PCF(Percentage-Closer Filtering): 对多个邻近深度值进行平均处理,可以平滑阴影边缘。

阴影贴图与软阴影

阴影贴图(Shadow Mapping)是生成阴影的基础技术,通过记录从光源视角下场景中每个像素到光源的距离,来判断像素是否在阴影中。通过添加一些额外的处理,阴影贴图技术也可以用来生成软阴影。

使用阴影贴图生成软阴影的方法

以下是几种常见的方法,用于在阴影贴图的基础上生成软阴影:

使用阴影贴图生成软阴影的方法

以下是几种常见的方法,用于在阴影贴图的基础上生成软阴影:

PCF(Percentage-Closer Filtering):

在计算阴影时,对当前像素周围的多个深度值进行采样,然后对这些深度值进行平均处理,以生成平滑的阴影边缘。

float shadow = 0.0;

vec2 texelSize = 1.0 / textureSize(shadowMap, 0);

for(int x = -1; x <= 1; ++x)

{

for(int y = -1; y <= 1; ++y)

{

float pcfDepth = texture(shadowMap, fragPosLightSpace.xy + vec2(x, y) * texelSize).r;

shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;

}

}

shadow /= 9.0;

VSM(Variance Shadow Maps):

使用方差阴影贴图技术,通过存储深度和深度的平方,使用方差来估计深度的分布,从而实现软阴影。

float ChebyshevUpperBound(float Moments[2], float t)

{

// One-tailed inequality valid if t > Moments[0]

float p = t - Moments[0];

float variance = Moments[1] - (Moments[0] * Moments[0]);

variance = max(variance, 0.00002);

float d = p * p / variance;

return variance / (variance + d);

}

面积光源采样(Area Light Sampling):

对光源进行多个采样,模拟面积光源的效果,从而生成软阴影。这个方法比较昂贵,因为需要进行多次采样。

float shadow = 0.0;

int samples = 16;

vec3 random = vec3(rand(), rand(), rand());

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

{

vec3 offset = gridSamplingDisk(i, samples, random);

float closestDepth = texture(shadowMap, fragPosLightSpace.xy + offset.xy).r;

shadow += currentDepth - bias > closestDepth ? 1.0 : 0.0;

}

shadow /= float(samples);

总结

软阴影是指具有平滑边缘的阴影,能够提供更逼真的渲染效果。阴影贴图是生成阴影的一种基础技术,通过一些扩展和处理,可以在阴影贴图的基础上生成软阴影。常见的方法包括PCF、VSM和面积光源采样等。通过这些技术,可以实现不同程度和质量的软阴影效果,以满足不同的渲染需求。

备注:

Unity阴影近处(B)的锯齿多,远处(A)的平滑的问题:阴影级联 - Unity 手册

相关推荐

ShukiKato手工折纸小飞龙的折法图谱教程(一)
世界杯365网站打不开

ShukiKato手工折纸小飞龙的折法图谱教程(一)

📅 07-14 👁️ 9293
野性的呼唤中文版字数?(野性的呼唤中文版字数有多少)
mobile365体育投注

野性的呼唤中文版字数?(野性的呼唤中文版字数有多少)

📅 08-03 👁️ 3741
双菱形折法图解-基本折法五
mobile365体育投注

双菱形折法图解-基本折法五

📅 07-08 👁️ 5444
word表格中如何添加斜线(怎么弄word表格斜线)
365bet最新备用网站

word表格中如何添加斜线(怎么弄word表格斜线)

📅 08-15 👁️ 4410
林志玲日本住所曝光,非豪宅居住,两人收入差距惊人
365bet最新备用网站

林志玲日本住所曝光,非豪宅居住,两人收入差距惊人

📅 09-29 👁️ 4573
为什么音响会嘟嘟嘟
mobile365体育投注

为什么音响会嘟嘟嘟

📅 08-29 👁️ 7459