Matlab:;X射线;通过面片绘制线

Matlab:;X射线;通过面片绘制线,matlab,plot,3d,Matlab,Plot,3d,问题 我试图可视化一个三维路径,以及它周围的“云”,它代表数据的标准偏差。我希望能够看到一条粗黑线,它周围有一个均匀的灰色区域,线没有任何云层,就像看到线穿过云层一样,就像看到x射线一样 尝试 我使用plot3创建了一条粗线,并使用patch创建了一系列以线的每个点为中心的框(在绘图上有一些额外的东西来表示开始/停止和方向,我也希望它们能很容易地被看到)。我试着玩补丁的alpha,但这会给线条造成一种混浊感,因此无论视线中有多少灰色框,灰色的亮度都会发生变化。我希望alpha为1,这样每个灰色框

问题

我试图可视化一个三维路径,以及它周围的“云”,它代表数据的标准偏差。我希望能够看到一条粗黑线,它周围有一个均匀的灰色区域,线没有任何云层,就像看到线穿过云层一样,就像看到x射线一样

尝试

我使用
plot3
创建了一条粗线,并使用
patch
创建了一系列以线的每个点为中心的框(在绘图上有一些额外的东西来表示开始/停止和方向,我也希望它们能很容易地被看到)。我试着玩
补丁的
alpha
,但这会给线条造成一种混浊感,因此无论视线中有多少灰色框,灰色的亮度都会发生变化。我希望
alpha
为1,这样每个灰色框的颜色都完全相同,但我希望找到某种方法使线均匀地穿过云层

最小示例

根据要求,这里是一个最小的示例,它生成下面的图

% Create a path as an example (a circle in the x-y plane, with sinusoidal deviations in the z-axis)
t = 0:1/100:2*pi;
x = sin(t);y = cos(t);
z = cos(t).*sin(5*t);
figure;
plot3(x,y,z,'k','linewidth',7);

% Draw patches
cloud = .1*rand(size(t)); % The size of each box (make them random, "like" real data)
grayIntensity = .9; % Color of patch
faceAlpha = .15; % Alpha of patch

for i = 1:length(x)
patch([x(i) - cloud(i); x(i) + cloud(i); x(i) - cloud(i); x(i) + cloud(i); x(i) - cloud(i); x(i) + cloud(i); x(i) - cloud(i); x(i) + cloud(i)],... % X values
[y(i) - cloud(i); y(i) - cloud(i); y(i) + cloud(i); y(i) + cloud(i); y(i) - cloud(i); y(i) - cloud(i); y(i) + cloud(i); y(i) + cloud(i)],... % Y values
[z(i) + cloud(i); z(i) + cloud(i); z(i) + cloud(i); z(i) + cloud(i); z(i) - cloud(i); z(i) - cloud(i); z(i) - cloud(i); z(i) - cloud(i)],... % Z values
grayIntensity*ones(1,3),... % Color of patch
'faces', [1 2 4 3;5 6 8 7;1 2 6 5; 8 7 3 4;1 5 7 3;2 6 8 4],... % Connect vertices to form faces (a box)
'edgealpha',0,... % Make edges invisible (to get continuous cloud effect)
'facealpha',faceAlpha); % Set alpha of faces
end
抱歉for循环中的代码太长,有相当多的参数指向
patch
命令。前三行只是定义定义立方体的8个顶点的x、y和z坐标,通过指定立方体的中心点加上或减去一半宽度,
cloud(i)
。其余部分应通过各自的评论加以解释


谢谢你的帮助

我的Matlab版本很旧,但类似的东西也很有希望对您有用:

  • 首先绘制多云的补丁,就像上面所做的那样(还没有绘制实体路径)
  • 存储轴句柄:

    patch_axis = gca;
    
  • 创建一个新的重叠轴

    path_axis = axes('position',get(patch_axis,'position'),'visible','off');
    
  • 绘制实体路径线(在此新轴中)

  • 将面片_轴(后面)的旋转和限制链接到路径_轴(前面)的旋转和限制:

  • 如果手动旋转视图,则在第一次调整后,视图应对齐两个轴,并保持它们对齐。但是,如果要使用命令设置旋转和限制,则可以在
    set
    命令中包含两个轴控制柄(面片轴和路径轴),或者在以下操作之后复制设置:

    set(patch_axis,{'view','xlim','ylim','zlim'}, ...
    get(path_axis,{'view','xlim','ylim','zlim'})
    

  • 请注意,要调整轴属性(标记标签等),需要对可见面片轴而不是不可见路径轴进行调整。如果您想让它与手动旋转实时交互,我不知道如何在每次重画时执行对齐功能。

    这里是我在评论中提到的解决方案的一个实现(只绘制一个
    曲面
    云)

    它没有经过优化,有一些
    for
    循环可以通过巧妙地使用bsxfun或这些辅助函数族来避免,但它运行正常。在每个点上找到曲线的切线和定向(旋转)每个横截面的数学可能也可以简化,但这不是我的强项,所以如果专家愿意,我将其留给他们

    基本上,它定义了一个圆(在代码中通常称为“横截面”),半径与某物成比例(应用程序中的标准偏差,示例中的随机值)。然后在3D中旋转每个圆,使其在平移点处与曲线垂直。然后将所有这些圆用作单个
    曲面
    图形对象的封套

    当曲面多次重叠时(取决于视图角度),主中心线仍有一点阴影,但主中心线始终可见。另外,您只有一个图形对象要管理

    结果看起来是这样的:

    当然,您可以根据自己的喜好更改曲面的
    AlphaValue
    。我定义了一个与颜色信息数据大小相同的完整矩阵。目前,它全部设置为
    0
    (因此它指向默认颜色贴图中的绿色),但通过这种方式,如果您想创建另一个参数的颜色函数,也很容易,只需相应地调整颜色矩阵(以及与之配套的颜色贴图)

    代码末尾有一个选项,可以将每个横截面显示为面片对象。它并不打算在最终结果中使用,但是如果您想进行自己的修改,它可以帮助您理解整个事情是如何构造的

    代码如下:

    %% // Create a path as an example (a circle in the x-y plane, with sinusoidal deviations in the z-axis)
    nPts = 180 ;
    t = linspace(0,359,nPts)*pi/180;
    x = sin(t); y = cos(t);
    z = cos(t).*sin(2*t);
    
    figure;
    h.line = plot3(x,y,z,'k','linewidth',2,'Marker','none');
    hold on
    xlabel('X')
    ylabel('Y')
    zlabel('Z')
    
    %% // Define options
    %// cloud = .1*rand(size(t)) ; % The size of each box (make them random, "like" real data)
    %// I used another randomization process, make that function of your stdev
    r.min = 0.1 ; r.max = 0.2 ;
    radius = r.min + (r.max-r.min).* rand(size(t)) ;
    
    %// define surface and patch display options (FaceAlpha etc ...), for later
    surfoptions  = {'FaceAlpha',0.2 , 'EdgeColor','none' , 'EdgeAlpha',0.1 , 'DiffuseStrength',1 , 'AmbientStrength',1 } ;
    patchoptions = {'FaceAlpha',0.2 , 'EdgeColor','k'    , 'EdgeAlpha',0.2 , 'DiffuseStrength',1 , 'AmbientStrength',1 } ;
    patchcol     = [1 0 0] ;  % Color of patch
    
    %% // get the gradient at each point of the curve
    Gx = diff([x,x(1)]).' ;                       %'//damn StackOverflow prettifier 
    Gy = diff([y,y(1)]).' ;                       %'//damn StackOverflow prettifier 
    Gz = diff([z,z(1)]).' ;                       %'//damn StackOverflow prettifier 
    %// get the middle gradient between 2 segments (optional, just for better rendering if low number of points)
    G = [ (Gx+circshift(Gx,1))./2 (Gy+circshift(Gy,1))./2 (Gz+circshift(Gz,1))./2] ;
    
    %% // get the angles (azimuth, elevation) of each plane normal to the curve
    
    ux = [1 0 0] ;
    uy = [0 1 0] ;
    uz = [0 0 1] ;
    
    for k = nPts:-1:1 %// running the loop in reverse does automatic preallocation
        a = G(k,:) ./ norm(G(k,:)) ;
        angx(k) =  atan2( norm(cross(a,ux)) , dot(a,ux))  ;
        angy(k) =  atan2( norm(cross(a,uy)) , dot(a,uy))  ;
        angz(k) =  atan2( norm(cross(a,uz)) , dot(a,uz))  ;
    
        [az(k),el(k)] = cart2sph( a(1) , a(2) , a(3) ) ;
    end
    %// adjustment to be normal to cross section plane the way the rotation are defined later
    az = az + pi/2 ; 
    el = pi/2 - el ;
    
    %% // define basic disc
    discResolution = 20 ;
    tt = linspace( 0 , 2*pi , discResolution ) ;
    xd = cos(tt) ;
    yd = sin(tt) ;
    zd = zeros( size(xd) ) ;
    
    %% // Generate coordinates for each cross section
    
    ccylX = zeros( nPts , discResolution ) ;
    ccylY = zeros( nPts , discResolution ) ;
    ccylZ = zeros( nPts , discResolution ) ;
    ccylC = zeros( nPts , discResolution ) ;
    
    for ip = 1:nPts
        %// cross section coordinates, with radius function of [rand] in this
        %// example. Make it function of the stdev in your application.
        csTemp = [ ( radius(ip) .* xd )  ; ... %// X coordinates
                   ( radius(ip) .* yd )  ; ... %// Y coordinates
                                   zd    ] ;   %// Z coordinates
    
        %// rotate the cross section (around X axis, around origin)
        elev = el(ip) ;
        Rmat = [ 1     0           0     ; ...
                 0 cos(elev)  -sin(elev) ; ...
                 0 sin(elev)   cos(elev) ] ;
        csTemp = Rmat * csTemp ;
    
        %// do the same again to orient the azimuth (around Z axis)
        azi = az(ip) ;
        Rmat = [ cos(azi)  -sin(azi) 0 ; ...
                 sin(azi)   cos(azi) 0 ; ...
                   0            0    1 ] ;
        csTemp = Rmat * csTemp ;
    
        %// translate each cross section where it should be and store in global coordinate vector
        ccylX(ip,:) = csTemp(1,:) + x(ip) ;
        ccylY(ip,:) = csTemp(2,:) + y(ip) ;
        ccylZ(ip,:) = csTemp(3,:) + z(ip) ;
    end
    
    %% // Display the full cylinder
    hd.cyl = surf( ccylX , ccylY , ccylZ , ccylC ) ;
    
    %// use that if the graphic object already exist but you just want to update your data:
    %// set( hd.cyl , 'XData',ccylX , 'YData',ccylY ,'ZData',ccylZ ) 
    
    set( hd.cyl , surfoptions{:} )
    
    
    %% // this is just to avoid displaying the patches in the next block
    %// comment the "return" instruction or just execute next block if you want
    %// to see the building cross sections as patches
    return 
    
    %% // display patches
    hp = zeros(nPts,1) ;
    for ip = 1:nPts
       hp(ip) = patch( ccylX(ip,:) , ccylY(ip,:) , ccylZ(ip,:) , patchcol ) ;
       set( hp(ip) , patchoptions{:} )
    end
    

    这只是一个快速缩放的视图,带有补丁(代码以较少的点数重新运行,否则会很快使整个图形变得混乱):


    我认为(不确定)您可以使用zbuffer渲染模式来实现这一点。因此,您
    设置(gca,'Renderer','zbuffer')
    并首先绘制灰色部分,然后绘制黑色部分。我不确定它是否会起作用,但它可能……我曾经有过类似的问题。我只创建了一个曲面来解决这个问题,表示云的封套(由沿中心线的所有圆盘/盒子定义)。然后,您只需要管理一个对象,并且许多对象的alpha值不会相加,因此主中心线非常清晰可见。“不过,你需要提供更多的数据,才能将其应用到你的案例中。”安德烈比古里,好主意。我尝试了你的解决方案,但它只在2D中起作用。在3D中,即使您将线条的控制柄放在uistack的顶部(或最后绘制),渲染器也会检测到线条“在”另一个对象(相对于相机)后面的部分,并且不会为该隐藏部分渲染线条。@Hoki-mmm您是正确的。我试图寻找一种方法来禁用Zbuffer,因为它可以在使用openGL时实现,但我找不到一种方法来实现它…@Hoki这是一个好主意,但是尝试和减少它可能有点复杂
    %% // Create a path as an example (a circle in the x-y plane, with sinusoidal deviations in the z-axis)
    nPts = 180 ;
    t = linspace(0,359,nPts)*pi/180;
    x = sin(t); y = cos(t);
    z = cos(t).*sin(2*t);
    
    figure;
    h.line = plot3(x,y,z,'k','linewidth',2,'Marker','none');
    hold on
    xlabel('X')
    ylabel('Y')
    zlabel('Z')
    
    %% // Define options
    %// cloud = .1*rand(size(t)) ; % The size of each box (make them random, "like" real data)
    %// I used another randomization process, make that function of your stdev
    r.min = 0.1 ; r.max = 0.2 ;
    radius = r.min + (r.max-r.min).* rand(size(t)) ;
    
    %// define surface and patch display options (FaceAlpha etc ...), for later
    surfoptions  = {'FaceAlpha',0.2 , 'EdgeColor','none' , 'EdgeAlpha',0.1 , 'DiffuseStrength',1 , 'AmbientStrength',1 } ;
    patchoptions = {'FaceAlpha',0.2 , 'EdgeColor','k'    , 'EdgeAlpha',0.2 , 'DiffuseStrength',1 , 'AmbientStrength',1 } ;
    patchcol     = [1 0 0] ;  % Color of patch
    
    %% // get the gradient at each point of the curve
    Gx = diff([x,x(1)]).' ;                       %'//damn StackOverflow prettifier 
    Gy = diff([y,y(1)]).' ;                       %'//damn StackOverflow prettifier 
    Gz = diff([z,z(1)]).' ;                       %'//damn StackOverflow prettifier 
    %// get the middle gradient between 2 segments (optional, just for better rendering if low number of points)
    G = [ (Gx+circshift(Gx,1))./2 (Gy+circshift(Gy,1))./2 (Gz+circshift(Gz,1))./2] ;
    
    %% // get the angles (azimuth, elevation) of each plane normal to the curve
    
    ux = [1 0 0] ;
    uy = [0 1 0] ;
    uz = [0 0 1] ;
    
    for k = nPts:-1:1 %// running the loop in reverse does automatic preallocation
        a = G(k,:) ./ norm(G(k,:)) ;
        angx(k) =  atan2( norm(cross(a,ux)) , dot(a,ux))  ;
        angy(k) =  atan2( norm(cross(a,uy)) , dot(a,uy))  ;
        angz(k) =  atan2( norm(cross(a,uz)) , dot(a,uz))  ;
    
        [az(k),el(k)] = cart2sph( a(1) , a(2) , a(3) ) ;
    end
    %// adjustment to be normal to cross section plane the way the rotation are defined later
    az = az + pi/2 ; 
    el = pi/2 - el ;
    
    %% // define basic disc
    discResolution = 20 ;
    tt = linspace( 0 , 2*pi , discResolution ) ;
    xd = cos(tt) ;
    yd = sin(tt) ;
    zd = zeros( size(xd) ) ;
    
    %% // Generate coordinates for each cross section
    
    ccylX = zeros( nPts , discResolution ) ;
    ccylY = zeros( nPts , discResolution ) ;
    ccylZ = zeros( nPts , discResolution ) ;
    ccylC = zeros( nPts , discResolution ) ;
    
    for ip = 1:nPts
        %// cross section coordinates, with radius function of [rand] in this
        %// example. Make it function of the stdev in your application.
        csTemp = [ ( radius(ip) .* xd )  ; ... %// X coordinates
                   ( radius(ip) .* yd )  ; ... %// Y coordinates
                                   zd    ] ;   %// Z coordinates
    
        %// rotate the cross section (around X axis, around origin)
        elev = el(ip) ;
        Rmat = [ 1     0           0     ; ...
                 0 cos(elev)  -sin(elev) ; ...
                 0 sin(elev)   cos(elev) ] ;
        csTemp = Rmat * csTemp ;
    
        %// do the same again to orient the azimuth (around Z axis)
        azi = az(ip) ;
        Rmat = [ cos(azi)  -sin(azi) 0 ; ...
                 sin(azi)   cos(azi) 0 ; ...
                   0            0    1 ] ;
        csTemp = Rmat * csTemp ;
    
        %// translate each cross section where it should be and store in global coordinate vector
        ccylX(ip,:) = csTemp(1,:) + x(ip) ;
        ccylY(ip,:) = csTemp(2,:) + y(ip) ;
        ccylZ(ip,:) = csTemp(3,:) + z(ip) ;
    end
    
    %% // Display the full cylinder
    hd.cyl = surf( ccylX , ccylY , ccylZ , ccylC ) ;
    
    %// use that if the graphic object already exist but you just want to update your data:
    %// set( hd.cyl , 'XData',ccylX , 'YData',ccylY ,'ZData',ccylZ ) 
    
    set( hd.cyl , surfoptions{:} )
    
    
    %% // this is just to avoid displaying the patches in the next block
    %// comment the "return" instruction or just execute next block if you want
    %// to see the building cross sections as patches
    return 
    
    %% // display patches
    hp = zeros(nPts,1) ;
    for ip = 1:nPts
       hp(ip) = patch( ccylX(ip,:) , ccylY(ip,:) , ccylZ(ip,:) , patchcol ) ;
       set( hp(ip) , patchoptions{:} )
    end