三维网格精简算法(Quadric Error Metrics)附源码
在计算机图形应用中,为了尽可能真实呈现虚拟物体,往往需要高精度的三维模型。然而,模型的复杂性直接关系到它的计算成本,因此高精度的模型在几何运算时并不是必须的,取而代之的是一个相对简化的三维模型,那么如何自动计算生成这些三维简化模型就是网格精简算法所关注的目标。
[Garland et al. 1997]提出了一种基于二次误差作为度量代价的边收缩算法,其计算速度快并且简化质量较高。该方法在选择一条合适的边进行迭代收缩时,定义了一个描述边收缩代价的变量Δ,具体如下:对于网格中的每个顶点v,我们预先定义一个4×4的对称误差矩阵Q,那么顶点v = [vx vy vz 1]T的误差为其二次项形式Δ(v) = vTQv。假设对于一条收缩边(v1, v2),其收缩后顶点变为vbar,我们定义顶点vbar的误差矩阵Qbar为Qbar = Q1 + Q2,对于如何计算顶点vbar的位置有两种策略:一种简单的策略就是在v1, v2和(v1+ v2)/2中选择一个使得收缩代价Δ(vbar)最小的位置。另一种策略就是数值计算顶点vbar位置使得Δ(vbar)最小,由于Δ的表达式是一个二次项形式,因此令一阶导数为0,即
,该式等价于求解:

其中qij为矩阵Qbar中对应的元素。如果系数矩阵可逆,那么通过求解上述方程就可以得到新顶点vbar的位置,如果系数矩阵不可逆,就通过第一种简单策略来得到新顶点vbar的位置。根据以上描述,算法流程如下:

那么剩下的问题就是如何计算每个顶点的初始误差矩阵Q,在原始网格模型中,每个顶点可以认为是其周围三角片所在平面的交集,也就是这些平面的交点就是顶点位置,我们定义顶点的误差为顶点到这些平面的距离平方和:

其中p = [a b c d]T代表平面方程ax + by + cz + d = 0(a2 + b2 + c2 = 1)的系数,Kp为二次基本误差矩阵:

因此原始网格中顶点v的初始误差为Δ(v) = 0,当边收缩后,新顶点误差为Δ(vbar) = vbarTQbarvbar,我们依次选取收缩后新顶点误差最小的边进行迭代收缩直到满足要求为止。
%% surface simplification using quadratic error metrics
clc
clear all
close all load('bunny.mat'); V = surfdata.point;
F = surfdata.face; nv = size(V,); % total vertex number
np = 0.1*nv; % remained vertex number % fundamental error quadric
N = normals(V,F);
N = normalizeVector3d(N);
p = [N, -sum(N .* V(F(:,),:), )];
Q0 = bsxfun(@times, permute(p, [,,]), permute(p, [,,])); % compute the Q matrices for all the initial vertices.
nf = size(F,);
Q = zeros(,,nv);
valence = zeros(nv,);
for i = :nf
for j = :
valence(F(i,j)) = valence(F(i,j)) + ;
Q(:,:,F(i,j)) = Q(:,:,F(i,j)) + Q0(:,:,i);
end
end % all valid pairs
E = edges(F); % compute Q1+Q2 for each pair
Qbar = Q(:,:,E(:,)) + Q(:,:,E(:,)); % a simple scheme: select either v1, v2 or (v1+v2)/
ne = size(E,);
v1 = permute([V(E(:,),:),ones(ne,)], [,,]);
v2 = permute([V(E(:,),:),ones(ne,)], [,,]);
vm = 0.5 .* (v1 + v2);
v = [v1, v2, vm]; cost = zeros(ne,);
cost(:,) = sum(squeeze(sum(bsxfun(@times,v1,Qbar),)).*squeeze(v1),)';
cost(:,) = sum(squeeze(sum(bsxfun(@times,v2,Qbar),)).*squeeze(v2),)';
cost(:,) = sum(squeeze(sum(bsxfun(@times,vm,Qbar),)).*squeeze(vm),)'; figure;
draw_t = ;
num = nv;
tic
for i = :nv-np
if (nv - i) < 0.9*num
num = nv - i; clf
drawMesh(V, F, 'facecolor','y', 'edgecolor','k', 'linewidth', 1.2);
view([ ])
axis equal
axis off
camlight
lighting gouraud
cameramenu
drawnow
end [min_cost, vidx] = min(cost,[],);
[useless, k] = min(min_cost);
e = E(k,:); % update position for v1
V(e(),:) = v(:, vidx(k), k)';
V(e(),:) = nan; % update Q for v1
Q(:,:,e()) = Q(:,:,e()) + Q(:,:,e());
Q(:,:,e()) = nan; % updata face
F(F == e()) = e();
f_remove = sum(diff(sort(F,),[],) == , ) > ;
F(f_remove,:) = []; % collapse and delete edge and related edge information
E(E == e()) = e();
E(k,:) = [];
cost(k,:) = [];
Qbar(:,:,k) = [];
v(:,:,k) = []; % delete duplicate edge and related edge information
[E,ia,ic] = unique(sort(E,), 'rows');
cost = cost(ia,:);
Qbar = Qbar(:,:,ia);
v = v(:,:,ia); % pairs involving v1
pair = sum(E == e(), ) > ;
npair = sum(pair); % updata edge information
Qbar(:,:,pair) = Q(:,:,E(pair,)) + Q(:,:,E(pair,)); pair_v1 = permute([V(E(pair,),:),ones(npair,)], [,,]);
pair_v2 = permute([V(E(pair,),:),ones(npair,)], [,,]);
pair_vm = 0.5 .* (pair_v1 + pair_v2);
v(:,:,pair) = [pair_v1, pair_v2, pair_vm]; cost(pair,) = sum(squeeze(sum(bsxfun(@times,pair_v1,Qbar(:,:,pair)),)).*squeeze(pair_v1),)';
cost(pair,) = sum(squeeze(sum(bsxfun(@times,pair_v2,Qbar(:,:,pair)),)).*squeeze(pair_v2),)';
cost(pair,) = sum(squeeze(sum(bsxfun(@times,pair_vm,Qbar(:,:,pair)),)).*squeeze(pair_vm),)'; fprintf('%d\n', i);
end clf
drawMesh(V, F, 'facecolor','y', 'edgecolor','k', 'linewidth', 1.2);
view([ ])
axis equal
axis off
camlight
lighting gouraud
cameramenu

本文为原创,转载请注明出处:http://www.cnblogs.com/shushen
参考文献:
[1] Michael Garland and Paul S. Heckbert. 1997. Surface simplification using quadric error metrics. In Proceedings of the 24th annual conference on Computer graphics and interactive techniques (SIGGRAPH '97). ACM Press/Addison-Wesley Publishing Co., New York, NY, USA, 209-216.
三维网格精简算法(Quadric Error Metrics)附源码的更多相关文章
- swfupload多文件上传[附源码]
swfupload多文件上传[附源码] 文件上传这东西说到底有时候很痛,原来的asp.net服务器控件提供了很简单的上传,但是有回传,还没有进度条提示.这次我们演示利用swfupload多文件上传,项 ...
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生
[转].NET(C#):浅谈程序集清单资源和RESX资源 目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...
- 8个前沿的 HTML5 & CSS3 效果【附源码下载】
作为一个前沿的 Web 开发者,对于 HTML5 和 CSS3 技术或多或少都有掌握.前几年这些新技术刚萌芽的时候,开发者们已经使用它们来小试牛刀了,如今这些先进技术已经遍地开发,特别是在移动端大显身 ...
- C#编程总结(七)数据加密——附源码
C#编程总结(七)数据加密——附源码 概述 数据加密的基本过程就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码,通常称为“密文”,使其只能在输入相应的密钥之后才能显示出本来内容 ...
- Remote验证及其改进(附源码)
Remote验证及其改进(附源码) 表单中的输入项,有些是固定的,不变的验证规则,比如字符长度,必填等.但有些是动态的,比如注册用户名是否存在这样的检查,这个需要访问服务器后台才能解决.这篇文章将会介 ...
- javaweb异常提示信息统一处理(使用springmvc,附源码)
一.前言 后台出现异常如何友好而又高效地回显到前端呢?直接将一堆的错误信息抛给用户界面,显然不合适. 先不考虑代码实现,我们希望是这样的: (1)如果是页面跳转的请求,出现异常了,我们希望跳转到一个异 ...
- 13行代码实现:Python实时视频采集(附源码)
一.前言 本文是<人脸识别完整项目实战>系列博文第3部分:程序设计篇(Python版),第1节<Python实时视频采集程序设计>,本章内容系统介绍:基于Python+open ...
- 日志组件Log2Net的介绍和使用(附源码开源地址)
Log2Net是一个用于收集日志到数据库或文件的组件,支持.NET和.NetCore平台. 此组件自动收集系统的运行日志(服务器运行情况.在线人数等).异常日志.程序员还可以添加自定义日志. 该组件支 ...
- openlayers5-webpack 入门开发系列结合 echarts4 实现散点图(附源码下载)
前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...
随机推荐
- ASP.NET 访问共享文件夹
配置代码: var dataProtection = new Microsoft.AspNet.DataProtection.DataProtectionProvider(new DirectoryI ...
- 【Oracle 集群】Linux下Oracle RAC集群搭建之Oracle DataBase安装(八)
Oracle 11G RAC数据库安装(八) 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习的汇总 ...
- linux内核调试技术之修改内核定时器来定位系统僵死问题
1.简介 在内核调试中,会经常出现内核僵死的问题,也就是发生死循环,内核不能产生调度.导致内核失去响应.这种情况下我们可以采用修改系统内核中的系统时钟的中断来定位发生僵死的进程和函数名称.因为内核系统 ...
- C#中的委托解析
谈及到C#的基本特性,“委托”是不得不去了解和深入分析的一个特性.对于大多数刚入门的程序员谈到“委托”时,都会想到“将方法作为方法的参数进行传递”,很多时候都只是知道简单的定义,主要是因为“委托”在理 ...
- 深入浅出JavaScript之this
JavaScript中的this比较灵活,根据在不同环境下,或者同一个函数在不同方式调用下,this都有可能是不同的.但是有一个总的原则,那就是this指的是,调用函数的那个对象. 下面是我的学习笔记 ...
- C#基础回顾(一)—C#访问修饰符
一.写在前面的话 好久没有停下来总结自己,转眼间15年过去好些天,回首过去的日子,亦或失去,亦或所得!生活的节奏,常常让我们带着急急忙忙的节奏去追赶,也许这并不是每个人所期望的生活方式!于他人,于自己 ...
- 线程安全性:num++操作为什么也会出问题?
线程的安全性可能是非常复杂的,在没有充足同步的情况下,由于多个线程中的操作执行顺序是不可预测的,甚至会产生奇怪的结果(非预期的).下面的Tools工具类的plus方法会使计数加一,为了方便,这里的nu ...
- SET NOCOUNT 怎么理解
参考文章:http://www.cnblogs.com/si812cn/archive/2008/06/11/1217113.html 我简单的理解就是: 执行sql语句时 SET NOCOUNT O ...
- 在DevExpress程序中使用TeeList控件以及节点查询的处理
在很多情况下,我们需要通过树列表进行数据的展示,如一些有层次关系的数据,通过有层级的展示,能够使用户更加直观查看和管理相关的数据.在一般Winform开发的情况下,可以使用微软的TreeView控件, ...
- Elastic学习第一天遇到的问题以及添加的一些操作
1.刚开始安装好了之后,启动之后, 报错: ERROR: max file descriptors [] ] 需要设置max file descriptors为65536,出现这个是因为普通的用户是1 ...