shadow map 系列(pcf,csm,vsm,lvsm)

最近看了关于shadow map的论文,就最近总结一下:

 

Shadow Map :用于生成阴影

如果要了解shadow map,最好的办法就是去看dx的demo 。其算法大意如下:

运用渲染到纹理方法(render to target),以场景中的光源为坐标原点,建立光源坐标系,从而可以得到相对于光源的场景深度信息,将其保存在render target上(一般是R32F的surface),之所以选择R32F是考虑到场景的精度问题。这里的R32F就是我们所说的Shadow Map 了,其保存着相对于光源的场景深度信息。

接下来,运用系统默认的render target ,启用depth buffer,将场景中的物体(用世界坐标表示)转化到以光源为基准的投影坐标中,这个变化是这样的:世界坐标--〉物体坐标--〉光源坐标--〉光源视坐标--〉光源投影坐标 。然后比较深度,此时在pixel shader里,深度小于shadow map里相对点深度的,就是光源照到的地方(值为1),否则就是在阴影区(值为0)。注意这里比较深度值时,要将坐标转换到窗口坐标,毕竟shadow map是作为render target产生的。这样值为1的地方就输出颜色,而为0的地方就直接输出黑色了,就是阴影。

优点和缺点:

优点:不用想volume shadow那样去计算场景几何物体的形状,对于复杂场景只需要产生一张map。

缺点:阴影有锯齿,究其原因在于比较深度时,只有0和1 的输出,这样在阴影边缘就会有1和0的交替出现,从而产生锯齿。这也成为图形领域研究的热点。

 

PCF(Percentage Closer Filter ) :处理运用Shadow Map产生的阴影锯齿。

大致意思: 就是将深度比较产生的0和1的值放到一个render target里,然后对其进行过滤,这样阴影边缘区域就会出现0.2,0.5....等的灰度值,从而产生了soft shadow。 但是这种过滤方法消耗时间,特别是采样数目比较大的时候。后来简化成横向过滤和纵向过滤,从而减少采样数目。

 

CSM(convolution shadow map): 卷积shadow map。此方法是运用基于阴影重建,因为深度测试要么为0,要么为1 ,从而可以构造出一个x坐标为[-1,1],y坐标为[0,1]的函数,对此函数进行fft变换,运用基准函数(sin和cos)来重新构建Shadow Map 。 此方法占用内存比较多,需要多个render target。

 

VSM(variance shadow map):方差shadow map。此方法运用了切尔雪夫不等式,通过shadow map中每个象素的深度值期望和方差来计算灰度值。这是一种估计方法。注意这里的深度值期望是对shadow map进行过滤(横向和纵向)后,得到的深度值。还有这里的shadow map里是两个通道,一个保存深度值,另一个保存深度值的平方。所以过滤后,深度值的期望和深度值平方的期望都能得到了。这样就便于求方差,从而利用切尔雪夫不等式来求每个象素的值。深度值小于期望的,就是光源照到的地方,深度值大于期望的,就是阴影区。

优点:无需多个render target,一个就行。同时效果还不错。

缺点:当多个阴影重合时就出现"light bleeding"。这种artifact也是由于切尔雪夫不等式所引起的。所以以后就有了layers variance shadow map 来解决"light bleeding"

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
下面是一份基于Adda系列PCF8591的驱动程序,带有详细的注释: ```c #include <linux/init.h> // 驱动程序的初始化 #include <linux/module.h> // 使用模块化的方式编写驱动程序 #include <linux/i2c.h> // 使用I2C的通信方式 #include <linux/i2c-dev.h> // 使用I2C设备驱动 #include <linux/fs.h> // 文件系统相关的函数 #include <linux/types.h> // 提供数据类型的定义 #include <linux/cdev.h> // 字符设备相关的函数 #include <asm/uaccess.h> // 提供用户空间和内核空间之间拷贝数据的函数 // 定义I2C设备的地址 #define PCF8591_ADDR 0x48 // 定义驱动程序所操作的设备名称 static const char* driver_name = "pcf8591"; // 定义驱动程序对应的字符设备 static struct cdev pcf8591_cdev; // 定义I2C设备 static struct i2c_client* pcf8591_client; // 驱动程序的文件打开函数 static int pcf8591_open(struct inode* inode, struct file* filp) { // 添加打开设备时的操作 return 0; // 返回0表示成功 } // 驱动程序的文件读取函数 static ssize_t pcf8591_read(struct file* filp, char __user* buf, size_t count, loff_t* f_pos) { // 添加读取设备数据的操作 return count; // 返回读取的字节数 } // 驱动程序的文件写入函数 static ssize_t pcf8591_write(struct file* filp, const char __user* buf, size_t count, loff_t* f_pos) { // 添加写入设备数据的操作 return count; // 返回写入的字节数 } // 驱动程序的文件关闭函数 int pcf8591_release(struct inode* inode, struct file* filp) { // 添加关闭设备时的操作 return 0; // 返回0表示成功 } // 驱动程序的文件操作结构体 static struct file_operations pcf8591_fops = { .owner = THIS_MODULE, .open = pcf8591_open, .read = pcf8591_read, .write = pcf8591_write, .release = pcf8591_release, }; // 驱动程序的初始化函数 static int pcf8591_init(void) { // 注册字符设备 int ret = register_chrdev_region(MKDEV(0, 0), 1, driver_name); if (ret) { printk(KERN_ALERT "Failed to register device\n"); return ret; } // 分配字符设备结构体 cdev_init(&pcf8591_cdev, &pcf8591_fops); ret = cdev_add(&pcf8591_cdev, MKDEV(0, 0), 1); if (ret) { printk(KERN_ALERT "Failed to add device\n"); unregister_chrdev_region(MKDEV(0, 0), 1); return ret; } // 获取并注册I2C设备 struct i2c_adapter* pcf8591_adapter = i2c_get_adapter(0); pcf8591_client = i2c_new_device(pcf8591_adapter, NULL); i2c_put_adapter(pcf8591_adapter); if (!pcf8591_client) { printk(KERN_ALERT "Failed to register I2C device\n"); cdev_del(&pcf8591_cdev); unregister_chrdev_region(MKDEV(0, 0), 1); return -1; } printk(KERN_ALERT "pcf8591 driver initialized\n"); return 0; } // 驱动程序的卸载函数 static void pcf8591_exit(void) { // 注销I2C设备 i2c_unregister_device(pcf8591_client); // 注销字符设备 cdev_del(&pcf8591_cdev); unregister_chrdev_region(MKDEV(0, 0), 1); printk(KERN_ALERT "pcf8591 driver exited\n"); } // 指定驱动程序的初始化和卸载函数 module_init(pcf8591_init); module_exit(pcf8591_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Driver for adda PCF8591"); ``` 这份驱动程序使用了Linux内核提供的I2C和字符设备相关的函数,通过I2C通信方式与PCF8591芯片进行通信。驱动程序提供了打开、读取、写入和关闭设备的操作,并在初始化时注册了一个字符设备供用户空间进行访问。驱动程序的初始化函数和卸载函数在模块初始化和卸载时被调用,分别用来注册和注销设备。详细的注释说明了每个函数的功能和操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值