联级阴影贴图CSM(Cascaded shadow map)原理与实现

联级阴影贴图CSM(Cascaded shadow map)原理与实现

 

CSM是利用分层的ShadowMap技术,实现大场景的阴影算法。示意图如下图:

我们通过给眼视锥分片,为每个分片生成一个相同分辨率的光空间深度图。利用眼睛看到的片段,根据其所在分片位置,转换为相应光空间深度,和光空间深度图比对,若深度大于深度图内的深度,则处于阴影。

该算法分为4个步骤:

  1. 将眼视锥分成多个深度分片。
  2. 将光的视锥体分成多个较小的视锥体,每个视锥体(也可以称为包围盒)都覆盖一个分片。
  3. 为每个光视锥体渲染一个阴影贴图。
  4. 渲染整个场景的阴影。

根据以上步骤,我们首先构建类:

#define CSM_MAX_SPLITS 4

class ShadowMap{
public:
    ShadowMap();
    ~ShadowMap();
    void init(Camera* camera);
    //生成CSM所需的资源(分片光空间变换矩阵,眼空间到光空间变换矩阵)
    void pre_depth_write(Camera* camera, const glm::vec3& lightdir);
    
    glm::mat4 crop_matrix(int t_split_index);
    glm::mat4 projection_matrix(int t_split_index);
    glm::mat4 modelview_matrix();
    
    int num_splits() const;
    int depth_tex_size() const;
    
    GLuint fbo() const;
    GLuint texture() const;
    
    glm::vec4 far_bounds();
    float* texture_matrices();
   
private:
    void create_fbo();
    void create_texture();
    
    void update_split_distances(Camera* camera);
    //计算世界空间中的相机视锥切片边界点
    void update_split_frustum_points(Camera* camera);
    
    //生成光视锥切片的光空间变换矩阵
    void generate_crop_matrices(const glm::mat4& t_modelview);
    
    //更新每个分片far边界
    void update_far_bounds(const glm::mat4& projection);
    void update_texture_matrices(const glm::mat4& projection, const glm::mat4& view_inverse);
    
    GLuint m_fbo;//CSM的fbo
    GLuint m_texture_array;//texture数组
    int m_num_splits;//视锥分片数
    int m_depth_tex_size;//深度图尺寸
    float m_split_weight;//划分权重(lambda)
    float m_far_bounds[CSM_MAX_SPLITS];//相机空间的深度切面
    
    Frustum m_frustums[CSM_MAX_SPLITS];
    glm::mat4 m_bias;
    glm::mat4 m_modelview;//光空间view矩阵
    glm::mat4 m_crop_matrices[CSM_MAX_SPLITS];//分片光空间变换矩阵(投影*观察)
    glm::mat4 m_projection_matrices[CSM_MAX_SPLITS];//光空间投影矩阵
    glm::mat4 m_texture_matrices[CSM_MAX_SPLITS];//眼空间转换为光空间矩阵
};

初始化函数,非配摄像机要素,以及为眼视锥分片:

void ShadowMap::init(Camera* camera) {
    float camera_fov = camera->Zoom;
    float width = camera->Width;
    float height = camera->Height;
    float ratio = width / height;

    //略微添加偏移(这里为0.2),避免在边框处有伪像
    for(int i = 0 ; i < m_num_splits ; i++) {
        m_frustums[i].fov(camera_fov + 0.2f);
        m_frustums[i].ratio(ratio);
    }

    //光空间的深度需要m_bias将[-1,1]变换为[0,1],从而在texture中使用。
    m_bias = glm::mat4(
        0.5f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.5f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.5f, 0.0f,
        0.5f, 0.5f, 0.5f, 1.0f
    );

    //计算摄像机视线空间中每个平截头体切片的近距离和远距离
    update_split_distances(camera);

    create_fbo();
    create_texture();
}

 

眼视锥分片

“Parallel-Split Shadow Maps for Large-scale Virtual Environments”这篇文章给出了一个非常好的眼视锥分片方法,称为PSSM,即平行分片阴影贴图。为了理解其公式由来,我们看下图:

我们的关键问题是如何指定分割位置,即计算Ci。在上图中,从表面(橙色线段)投射到屏幕上的观察光束dp的大小约为ndy/z。φ和θ分别表示曲面法线与屏幕和阴影贴图平面之间的角度。由于dy =dzcosφ/cosθ,因此阴影图锯齿误差dp/ds(阴影贴图的尺寸为ds×ds)可以表示为:

当dp大于屏幕的像素大小时,会出现阴影贴图欠采样,这可能在透视误差dz/zds或投影误差cosφ/cosθ变大时发生。文章中提出了三种分割方案,即计算Ci的方案。

 

对数拆分方案

理想情况下,透视误差的最佳分布可使dp/ds在整个深度范围内保持恒定。即dz/zds =ρ(忽略投影误差),其中ρ为常数。有:

在这里,有一个重要的注意事项:阴影贴图s∈[0,1]的局部参数化意味着一个假设“阴影贴图准确地覆盖了视锥,并且在场景的不可见部分上没有浪费任何分辨率”。这意味着上述参数化只能在理论上实现。根据s∈[0,1]的假设,很容易得到ρ= ln(f / n)。因此,理论上最佳的透视阴影图参数化s(z)应满足以下对数函数:

但是,当前硬件支持的唯一非线性变换是透视变换(s = A/z+B)。为了近似对数函数,必须在几个深度层将其离散化。我们将眼视锥划分为更多的层,以使该函数具有更好的近似值。这是该文章中提出的“分片”思想的基本动机。

上述方程可以用z=\left \{ C_{i}^{log} \right \}进行离散化:

即:

因为此分片方案旨在产生透视误差理论上均匀的分布,所以分配给[Ci,Ci-1]的分辨率应为整体纹理分辨率的i/m。 将si = i/m代入上述公式可得出:

该分割方案的主要缺点是观看者附近的分割部分的长度太小,因此这些分割部分中可以包含的对象很少。这是由于理论上的最佳参数设置是假设阴影贴图准确地覆盖了视锥,并且没有任何分辨率浪费在场景的不可见部分上。换句话说,此假设要求每个z∈[n,f]必须在规范化纹理空间中映射到唯一的s∈[0,1]。但是,这种假设在实践中无法满足,以至于靠近观看者的部分发生过采样,而远离观看者的部分发生欠采样。因此,对数拆分方案很难在实践中应用。

 

均匀拆分方案

最简单的分割方案是沿z轴均匀放置分割平面,其分割点为:

忽略投影误差,我们获得下述阴影图锯齿误差:

可以看出当z减小时,阴影误差急剧增大。

 

加权拆分方案

文章提出的PSSM方法为将对数和均匀相加乘上二分之一,去获取较平衡的位置,而现如今的拆分多用对数和均匀的加权值,即:

引出了\lambda去人为调节想要的权重分配,从而获得误差减小和分辨率浪费之间的平衡。根据以上描述,我们的分片点选取代码如下:

//计算摄像机视线空间中每个平截头体切片的近距离和远距离
void ShadowMap::update_split_distances(Camera* camera) {
    float nd = camera->near;
    float fd = camera->far;

    float lambda = m_split_weight;
    float ratio = fd / nd;
    m_frustums[0].near(nd);

    //使用文章”Parallel-Split Shadow Maps for Large-scale Virtual Environments“的切片方法(PSSM)
    //文章使用均匀法和对数法的中值计算,而我们现在可以引用lambda去调节它们的比率。
    for(int i = 1 ; i < m_num_splits ; i++) {
        float si = i / (float)m_num_splits;
        float t_near = lambda * (nd * powf(ratio, si)) + (1 - lambda) * (nd + (fd - nd) * si);
        float t_far = t_near * 1.005f;//略微增加重合,避免断裂
        m_frustums[i].near(t_near);
        m_frustums[i-1].far(t_far);
    }
    m_frustums[m_num_splits-1].far(fd);
}

值得注意的是我们避免阴影断裂,适当的延长每个视锥切片的远平面,视锥类如下:

class Frustum {
public:
  glm::vec3 m_points[8];//平截头体8个点
  Frustum();
  void set(float t_fov, float t_ratio, float t_near, float t_far);
  float fov() const;
  float ratio() const;
  float near() const;
  float far() const;
  
  void near(float t_near);
  void far(float t_far);
  void fov(float t_fov);
  void ratio(float t_ratio);
    
private:
    float m_fov;
    float m_ratio;//宽高比
    float m_near;
    float m_far;
};
Frustum::Frustum() :
    m_fov(45.0),
    m_ratio(0.5),
    m_near(1.0),
    m_far(200.0) {
}
void Frustum::set(float t_fov, float t_ratio, float t_near, float t_far) {
  m_fov = t_fov;
  m_ratio = t_ratio;
  m_near = t_near;
  m_far = t_far;
}
float Frustum::fov() const {
  return m_fov;
}
float Frustum::ratio() const {
  return m_ratio;
}
float Frustum::near() const {
  return m_near;
}
float Frustum::far() const {
  return m_far;
}
void Frustum::near(float t_near) {
  m_near = t_near;
}
void Frustum::far(float t_far) {
  m_far = t_far;
}
void Frustum::fov(float t_fov) {
  m_fov = t_fov;
}
void Frustum::ratio(float t_ratio) {
  m_ratio = t_ratio;
}

 

构建光视锥体

完成了分割点的选取后,我们需要在光空间生成投影和观察矩阵,以便之后在光空间下的深度比对和渲染深度图。该部分主要分为以下四个小步骤:

  1. 更新摄像机视锥分块的世界空间位置(8个顶点)
  2. 生成光空间下的摄像机分块包围盒投影矩阵
  3. 更新分片位置(相机空间,标准化为[0,1])
  4. 更新眼空间到光空间变换矩阵

我们构建光视锥体代码如下:

//生成CSM所需的资源(分片光空间变换矩阵,眼空间到光空间变换矩阵(负灯光向量))
void ShadowMap::pre_depth_write(Camera* camera, const glm::vec3& lightdir) {
    glm::mat4 t_modelview = glm::lookAt(
    glm::vec3(0.0, 0.0, 0.0),
    glm::vec3(-lightdir.x, -lightdir.y, -lightdir.z),
    glm::vec3(-1.0f, 0.0f, 0.0f));

    //更新摄像机视锥分块的世界空间位置(8个顶点)
    update_split_frustum_points(camera);

    //生成光空间的摄像机分片包围盒投影矩阵
    generate_crop_matrices(t_modelview);
    m_modelview = t_modelview;

    glm::mat4 t_view = camera->GetViewMatrix();
    glm::mat4 t_view_inverse = glm::inverse(t_view);
    glm::mat4 t_projection = camera->GetPerspectiveMatrix();

    //更新分片位置(相机空间,标准化为[0,1])
    update_far_bounds(t_projection);
    
    //更新眼空间到光空间变换矩阵
    update_texture_matrices(t_projection, t_view_inverse);
}

1.更新摄像机视锥分块的世界空间位置(8个顶点):

因为我们获得了分割点的位置,但是这个点位于眼睛空间,为了计算光投影锥体,我们获得其在世界空间的位置,并计算出每盒分块眼睛视锥体的8个顶点,用以之后的光投影锥体的构建。我们计算分块锥体8个顶点的代码如下:

//更新摄像机视锥分块的世界空间位置(8个顶点)
void ShadowMap::update_split_frustum_points(Camera* camera) {
    glm::vec3 center = camera->Position;
    glm::vec3 view_dir = -camera->Front;

    glm::vec3 up(0.0f, 1.0f, 0.0f);
    glm::vec3 right = glm::cross(view_dir, up);

    for(int i = 0 ; i < m_num_splits ; i++) {
        Frustum& t_frustum = m_frustums[i];

        glm::vec3 fc = center + view_dir * t_frustum.far();
        glm::vec3 nc = center + view_dir * t_frustum.near();

        right = glm::normalize(right);
          
        up = glm::normalize(glm::cross(right, view_dir));

        // 计算当前分片的近平面和远平面宽高的一半
        float near_height = tan(t_frustum.fov() / 2.0f) * t_frustum.near();
        float near_width = near_height * t_frustum.ratio();
        float far_height = tan(t_frustum.fov() / 2.0f) * t_frustum.far();
        float far_width = far_height * t_frustum.ratio();
          
        //记录眼视锥8个顶点
        t_frustum.m_points[0] = nc - up * near_height - right * near_width;
        t_frustum.m_points[1] = nc + up * near_height - right * near_width;
        t_frustum.m_points[2] = nc + up * near_height + right * near_width;
        t_frustum.m_points[3] = nc - up * near_height + right * near_width;
        t_frustum.m_points[4] = fc - up * far_height - right * far_width;
        t_frustum.m_points[5] = fc + up * far_height - right * far_width;
        t_frustum.m_points[6] = fc + up * far_height + right * far_width;
        t_frustum.m_points[7] = fc - up * far_height + right * far_width;
    }
}

我们使用摄像机的位置和方向,并利用简单的三角函数就能计算出每个视锥分块需要的8个顶点。

 

2.生成光空间的摄像机分块包围盒投影矩阵:

接下来非常重要的一步就是为每个眼视锥分块找到一个AABB,该包围盒位于光空间下。我们为每个AABB构造一个投影观察矩阵,之后的每一个眼视锥分块的深度图都是用与其对应的光空间投影观察矩阵生成。变换矩阵生成代码如下:

//生成光视锥切片的光空间变换矩阵(t_modelview为光观察矩阵)
void ShadowMap::generate_crop_matrices(const glm::mat4& t_modelview) {
    glm::mat4 t_projection;
    for(int i = 0 ; i < m_num_splits ; i++) {
        Frustum& t_frustum = m_frustums[i];

        glm::vec3 tmax(-1000.0f, -1000.0f, 0.0f);
        glm::vec3 tmin(1000.0f, 1000.0f, 0.0f);

        //寻找光空间八个顶点的最大最小z值
        glm::vec4 t_transf = t_modelview * glm::vec4(t_frustum.m_points[0], 1.0f);
        tmin.z = t_transf.z;
        tmax.z = t_transf.z;
        for(int j = 1 ; j < 8 ; j++) {
            t_transf = t_modelview * glm::vec4(t_frustum.m_points[j], 1.0f);
            if(t_transf.z > tmax.z) { tmax.z = t_transf.z; }
            if(t_transf.z < tmin.z) { tmin.z = t_transf.z; }
        }

        //可能在距离光源较近处存在遮挡物(不知道给啥值,给个大一点的吧)
        tmax.z += 10;

        //生成光的正交投影矩阵(长宽用单位1,每个视锥分片的光正交投影矩阵可以通过该单位宽高投影矩阵缩放平移后得到)
        glm::mat4 t_ortho = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -tmax.z, -tmin.z);
        glm::mat4 t_shad_mvp = t_ortho * t_modelview;

        //找到在光空间下视锥切片的(x,y)范围
        for(int j = 0 ; j < 8 ; j++) {
            t_transf = t_shad_mvp * glm::vec4(t_frustum.m_points[j], 1.0f);

            t_transf.x /= t_transf.w;
            t_transf.y /= t_transf.w;

            if(t_transf.x > tmax.x) { tmax.x = t_transf.x; }
            if(t_transf.x < tmin.x) { tmin.x = t_transf.x; }
            if(t_transf.y > tmax.y) { tmax.y = t_transf.y; }
            if(t_transf.y < tmin.y) { tmin.y = t_transf.y; }
        }
        glm::vec2 tscale(2.0f / (tmax.x - tmin.x), 2.0f / (tmax.y - tmin.y));
        glm::vec2 toffset(-0.5f * (tmax.x + tmin.x) * tscale.x, -0.5f * (tmax.y + tmin.y) * tscale.y);

        glm::mat4 t_shad_crop=glm::mat4(1.0);
        t_shad_crop[0][0] = tscale.x;
        t_shad_crop[1][1] = tscale.y;
        t_shad_crop[0][3] = toffset.x;
        t_shad_crop[1][3] = toffset.y;
        t_shad_crop = glm::transpose(t_shad_crop);//注意glm按列储存,实际矩阵要转置

        t_projection = t_shad_crop * t_ortho;

        //保存视锥切片的光投影矩阵
        m_projection_matrices[i] = t_projection;

        //保存世界坐标到光空间变换的矩阵(每个视锥切片保存一个自己的)
        m_crop_matrices[i] = t_projection * t_modelview;
    }
}

这里第一个需要注意的是tmax.z+=10。这是由于以下情况:

因此该值也可以定义一个设置接口,根据不同的场景,可随意调整该值。

在构造正交投影矩阵时,我们使用:

glm::mat4 t_ortho = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -tmax.z, -tmin.z);

创建了一个x,y∈[-1,1],z∈[-tmax.z,-tmin.z]的正交投影矩阵。首先,使用x,y∈[-1,1],是因为我们的每个分块矩阵都可以使用单位x,y缩放平移后获得。而我们设定z∈[-tmax.z,-tmin.z],是因为眼空间指向-Z方向,而glm::ortho传入的是近平面和远平面(指向+Z)。

平移缩放矩阵可以根据构造正交投影矩阵的原理得出,公式如下:

更详细的可以看我介绍投影矩阵的博客:https://blog.csdn.net/qq_39300235/article/details/90670282

计算完所有的分块光空间变换矩阵后,我们将其保存。

 

3.更新分片位置(相机空间,标准化为[0,1])

我们依旧可以使用投影矩阵的特点,直接计算标准化后的z,并变换至[0,1],详细介绍也在上述博客中。根据投影公式:

眼空间中w为1。所以我们更新分片在相机空间的位置代码如下:

//更新分片位置(相机空间,标准化为[0,1])
void ShadowMap::update_far_bounds(const glm::mat4& projection) {
    for(int i = 0 ; i < m_num_splits ; i++) {
        //将分片的位置标准化到(0,1),需要注意我们初识给的视锥分片是正值,然而利用投影矩阵标准化需要在眼空间上执行,
        //眼空间到Z方向为指向负方向,所以我们使用视锥分片位置需要乘-1。而w分量正好是-z,所以不需要乘
        m_far_bounds[i] =0.5f*((-1.0f*m_frustums[i].far()*projection[2][2]+projection[3][2])/
        m_frustums[i].far())+0.5f;
    }
}

 

4.更新眼空间到光空间变换矩阵

为了方便起见,我们还直接保存从眼空间到光空间的变换矩阵:


//更新眼空间到光空间的变换矩阵
void ShadowMap::update_texture_matrices(const glm::mat4& projection, const glm::mat4& view_inverse) {
    for(int i = 0 ; i < m_num_splits ; i++) {
        //为了比对眼空间的深度和光空间的深度,需要m_bias将[-1,1]变换为[0,1]
        m_texture_matrices[i] = m_bias * m_crop_matrices[i] * view_inverse;
    }
}

 

为每个光视锥体渲染一个阴影贴图

完成所有矩阵的获取后,我们可以为每一个光视锥体分块渲染一个深度图。这里我们的fbo和texture构造代码如下:

void ShadowMap::create_fbo() {
    glGenFramebuffers(1, &m_fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void ShadowMap::create_texture() {
    if(m_texture_array) {
        glDeleteTextures(1, &m_texture_array);
    }

    glGenTextures(1, &m_texture_array);
    glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_array);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT, depth_tex_size(), depth_tex_size(),CSM_MAX_SPLITS, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
}

写入深度的着色器很简单,我们不需要做其他的操作,只需要把世界空间转换为光视锥分块空间,代码如下:

顶点着色器:

#version 410 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

uniform mat4 lightSpaceMatrix;
uniform mat4 view;
uniform mat4 model;

void main(){
    gl_Position=lightSpaceMatrix*view*model*vec4(aPos,1.0f);
}

我们为每个光包围盒投影矩阵传入各自的lightSpaceMatrixd。

片元着色器:

#version 410 core

out vec4 FragColor;

void main(){

}

完成后,我们可以可视化我们写入的深度图,效果如下:

 

 

渲染整个场景的阴影

写入每个分块的深度图后,我们可以为整个场景渲染阴影。我们判断每个摄像机内的片元,根据其位于哪个分块内,将其转换为对应光空间,并与深度图的深度比对,如果大于深度图的深度,则处于阴影。我们的着色器代码如下:

顶点着色器:

#version 410 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 aTexCoord;

out vec2 texCoord;
out vec4 viewPos;
out vec4 farBounds;
out mat4 FragPosLightSpace[4];

uniform mat4 model;

layout (std140) uniform Matrices{
    vec4 afarBounds;
    mat4 projection;
    mat4 view;
    mat4 aFragPosLightSpace[4];
};

void main()
{
    gl_Position=projection*view*model*vec4(position, 1.0f);
    texCoord=aTexCoord;
    farBounds=afarBounds;
    for (int i=0; i<4; i++) {
        FragPosLightSpace[i]=aFragPosLightSpace[i];
    }
    viewPos=view*model*vec4(position, 1.0f);
}

片元着色器:

#version 410 core

in vec2 texCoord;
in vec4 viewPos;
in vec4 farBounds;
in mat4 FragPosLightSpace[4];

out vec4 FragColor;

uniform sampler2DArray shadowMap;
uniform vec2 texSize;

float shadowCal(vec4 viewPos);
void shadowJud();
void main()
{
    float shadow=shadowCal(viewPos);
    vec3 lighting=(1.0f-shadow)*vec3(0.5,0.5,0.5);
    FragColor = vec4(lighting,1.0f);
}

void shadowJud(){
    
}

float shadowCal(vec4 viewPos){
    int aIndex=3;
    if (gl_FragCoord.z<farBounds.x) {
        aIndex=0;
    }else if(gl_FragCoord.z<farBounds.y) {
        aIndex=1;
    }else if(gl_FragCoord.z<farBounds.z) {
        aIndex=2;
    }
    vec4 projCoords=FragPosLightSpace[aIndex]*viewPos;

    float closestDepth=texture(shadowMap,vec3(projCoords.xy,float(aIndex))).r;
    float currentDepth=projCoords.z;
    float bias=0.001;//阴影偏移,可以使所有片元在表面之上,消除失真
    
    float shadow=0.0f;
//    shadow=currentDepth-bias>closestDepth?1:0;
    vec2 texelSize=1.0/texSize;
    for(int x=-1;x<=1;x++){
        for(int y=-1;y<=1;y++){
            float pcfDepth=texture(shadowMap,vec3(projCoords.xy+vec2(x,y)*texelSize,float(aIndex))).r;
            shadow+=currentDepth-bias>pcfDepth?0.7f:0.0f;
        }
    }
    shadow/=9.0f;
    
    if(projCoords.z>1.0)//超出视界的z都大于1,大于深度贴图中的1.0,所以返回黑色,因此我们要加限制
        shadow=0.0f;
    
    return shadow;
}

我们这里做了简单的3*3的PCF,完成效果如下图:

我们可视化摄像机空间的分块效果,如下图:

我们实验中将视锥分为4片,而市面上许多游戏分三层就能达到非常好的阴影效果了。

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: CSM(Carrier-Sense Multiple Access)是一种广泛应用于无线通信系统中的多址接入技术。CSM宽带信号算法原理是通过多个终端用户共享同一频带资源的方式,实现数据传输的有效管理和调度。 其原理主要包括以下几个方面: 1. 载波感知:终端设备在发送数据之前先感知到信道上是否有其它设备正在传输数据。当一个终端设备想要发送数据时,会先监听信道上是否有载波信号。如果检测到信道上存在载波信号,则说明有其它设备正在进行数据传输,该设备需要等待。 2. 随机接入机制:当多个终端设备几乎同时试图访问信道时,可能会出现碰撞问题。为了解决这个问题,CSM采用了随机接入机制。具体操作是,设备在等待一段时间后随机选择一个时间进行尝试传输,以避免设备之间的碰撞。 3. 退避机制:如果发生碰撞,即多个设备同时开始传输数据而导致数据冲突,CSM会采取退避机制。退避机制是根据碰撞的次数来确定等待的时间间隔,每次发生碰撞后,设备会随机生成一个退避时间,然后等待该时间再次尝试传输,通过多次退避和重试过程,确保数据传输的成功。 4. 时隙划分:为了提高信道的利用率和传输效率,CSM常常将时间划分为一系列的时隙,每个设备占用一个时隙进行数据传输,以实现多设备同时传输数据。 综上所述,CSM宽带信号算法原理是通过载波感知、随机接入机制、退避机制和时隙划分等方式,实现多个终端设备在共享频带资源的情况下,有效进行数据传输的调度和管理。这种技术可以使得无线通信系统在多设备同时传输数据时能够避免碰撞和冲突,提高信道利用率和传输效率。 ### 回答2: CSM宽带信号算法(Carrier Sense Multiple Access,载波侦听多址)是一种在计算机网络中用于协调多个设备共享共同信道的方法。 CSM宽带信号算法的原理是作为一种随机接入技术,它通过侦听信道上的载波来判断信道是否被占用。当设备需要发送数据时,首先侦听信道是否有其他设备在使用,如果没有,它就可以发送数据。如果信道被占用,则设备必须等待一段随机时间后再进行侦听。 具体来说,CSM宽带信号算法包括以下几个步骤: 1. 侦听:设备通过侦听信道上的载波来判断信道是否被占用。如果侦听到载波,说明信道已经被其他设备占用,设备必须等待。 2. 等待:设备在侦听到载波后,会等待一段随机时间。这是为了避免多个设备同时等待完毕后同时发送数据,从而导致碰撞。 3. 再次侦听:等待随机时间后,设备再次侦听信道。如果信道仍然被占用,设备将再次等待一段随机时间后继续侦听。 4. 发送数据:当设备侦听到信道空闲时,它可以发送数据。发送完成后,设备会等待确认信号或者等待一段时间以确保数据已经被正确接收。 CSM宽带信号算法的优势在于它可以有效地避免多个设备同时发送数据导致的碰撞问题。每个设备在侦听到信道空闲后,都会等待一个随机时间,从而分散了设备的发送时间,降低了碰撞的概率。通过这种方式,CSM宽带信号算法可以实现多个设备在共享信道上高效地传输数据。 ### 回答3: CSM宽带信号算法是一种常用的频谱利用方法,通过对频谱资源进行分配和管理,实现多个用户之间的共享。其基本原理如下: 首先,CSM宽带信号算法将可用频谱资源按照一定的规则进行划分,使其能够同时支持多个用户的通信需求。这种划分可以基于时分复用(TDM)、频分复用(FDM)或者码分复用(CDM)等原理。 其次,CSM宽带信号算法利用时隙(slot)或者子载波(subcarrier)进行资源的分配。每个时隙或子载波可以被分配给一个用户进行传输,不同用户之间的传输可以同时进行,实现频谱的高效利用。 然后,CSM宽带信号算法通过一定的调度策略对各用户进行资源的动态分配。调度策略可以根据用户的通信需求、信道条件、优先级等因素进行确定,以实现公平性、高效性和质量保证。 最后,CSM宽带信号算法还可以通过一些技术手段对信号进行编码、调制和解调,以提高信号的传输效率和抗干扰性能。 总之,CSM宽带信号算法通过频谱资源的分配、调度和信号处理等过程,实现了多用户之间的共享传输,提高了频谱利用效率,满足了大量用户对宽带通信的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值