图形的光栅化_简述图像的采样和量化过程

图形的光栅化_简述图像的采样和量化过程在前面的学习中,我们已经可以通过MVP变换,把摄像机观测的物体都压缩成了一个标准立方体,接下来我们要做的是把这个标准立方体绘制到屏幕(Screen)上何为屏幕?一个二维数组,每个元素称之为像素(pixel,pictureelement的缩写),例如我们常说的屏幕分辨率1920*1080,就是说有这么些个像素。屏幕是一个典型的光栅成像设备。光栅(Raster)在德语中就是屏幕的意思,光栅化(Rasterize,名变动)就是把东西画在屏幕上。像素,最小单位,像素内的颜色可以用rgba来定义,一

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

前言

在前面的学习中,我们已经可以通过MVP变换,把摄像机观测的空间压缩成了一个标准立方体。而接下来我们要做的是把这个标准立方体绘制到屏幕上,这样才能最终被我们所看见。在做这步操作之前,我们首先要知道如下一些定义:

 

一些定义

屏幕(Screen)

在图形学中,我们把屏幕抽象为一个二维数组,数组中的每个元素称之为像素(pixel,picture element的缩写)。这个数组的大小,就是屏幕的分辨率(Resolution),例如我们常说的屏幕分辨率为1920*1080,就是说有这么些个像素。屏幕是一个典型的光栅成像设备(Raster display)。

光栅设备

示波器(Oscilloscope),阴极射线管(Cathode Ray Tube,早期的显示器),液晶显示器(Liquid Crystal Display,LCD,利用了光的波动性,液晶的扭曲),发光二极管(Light Emitting Diode,LED),墨水屏(Electrophoretic Display,刷新率很低)。

 

光栅与光栅化

光栅(Raster)在德语中就是屏幕的意思,光栅化(Rasterize)就是把东西画在屏幕上的过程。这个过程非常的复杂,也是本文的重点,会在后面进行介绍。

 

像素

像素,最小单位,我们可以把它理解为一个个小方块,像素内的颜色可以用rgba来定义,一个像素(小方块)内只存在一种颜色。

当然随着科技的发展,像素的内容以及分布也越来越复杂,例如下图为两个手机屏幕的像素分布:

图形的光栅化_简述图像的采样和量化过程

左图的苹果手机屏幕中,一个像素拥有三种颜色。

右图三星手机的这种分布我们称之为Bayer pattern,从中可以发现绿色的点比红蓝更多,这是因为人眼对绿色最敏感,人眼看上去可以更舒服。

在文本中依旧认为每个像素中只存在一种颜色。

 

定义屏幕空间

前面说了,我们要把标准立方体绘制到屏幕上,那么首先我们要定义一下屏幕空间,然后把标准立方体变换到这个空间上。

定义屏幕空间相当于在屏幕上建立一个坐标系,这里我们以屏幕的左下角为原点,向右为x轴方向,向上是y轴方向(注,定义的方法有很多种,例如我们也可以左上角为原点,在后续的操作中遵循自己的定义即可)。

 

图形的光栅化_简述图像的采样和量化过程

前面说到屏幕是有一个个像素所组成的,例如我们像素的二维数组为 w*h,那么就表示在x轴方向有w列,在y轴方向有h行。这里我们设每个像素的大小为1*1,那么整个屏幕的大小即为 w*h。如上图,一个个小方块即代表一个像素。

这样我们就可以通过坐标的方式来定义每个像素的位置了,即(x, y),可以当做是图中每个小方块左下角点的坐标。例如坐标(0, 0)就表示屏幕最左下角的那个像素,由于坐标从0开始,因此最右上角的那个像素坐标为(w-1, h-1)。

此外我们说过像素是一个个小方块,那么自然有它的中点(即图中小方块中间的点),因此像素(x, y)的中点即为(x+0.5, y+0.5)。

 

视口变换(Viewport Transform)

在前面我们定义好了屏幕空间,那么我们要把MVP变换后的标准立方体绘制到屏幕上,首先要做的就是把其变换到这个w*h的空间上,这个变换我们称之为视口变换。

该变换我们主要分为如下两步:

  1. 将x轴和y轴长度为2的标准立方体缩放为x轴和y轴长度分别为w和h长方体。
  2. 将该立方体从原点平移到 (\frac{w}{2},\frac{h}{2})

注:此处我们先不考虑z轴的变换,后续会有它的作用。

这个变换矩阵很简单,就不过多推导了,其结果如下:

T_{viewport}=\begin{bmatrix} \frac{w}{2} & 0& 0 & \frac{w}{2}\\ 0& \frac{h}{2} & 0 & \frac{h}{2}\\ 0 & 0& 1 &0 \\ 0 &0 &0 & 1 \end{bmatrix}

 

光栅化

在上面视口变换后,我们的标准立方体虽然变成了一个xy方向和屏幕一样大的空间,但是空间中依旧还是我们的三维物体,例如人,建筑,植物等。前面我们知道屏幕是由一个个像素组成的,也就是说我们通过屏幕看见的二维画面其实都是由无数个像素构成的。因此接下来我们要做的就是把空间中的那些三维物体全部打散成像素,而这个过程,我们就可以称之为光栅化。

 

Mesh与三角形

在生活中我们知道,不管是相机拍照还是人眼看,我们仅仅只能看见物体的表面,因此我们要显示在屏幕上的,也仅仅是这些三维物体的表面。而对于表面,我们可以把它理解成由多个不同平面所组成,例如长方体即是六个长方形所组成的。在图形学中,我们需要把表面分解成无数个不同的小三角形(Triangle),这些三角形像网一样编织在一起,就可以形成任何我们想要的三维物体表面,这些由三角形所构成的表面我们称之为Mesh。例如长方体就是有12个三角形组成,因为一个长方形可以分解成两个三角形。更复杂的物体表面分解可见下面几个示意图:

图形的光栅化_简述图像的采样和量化过程            图形的光栅化_简述图像的采样和量化过程     图形的光栅化_简述图像的采样和量化过程

 

为什么选择三角形呢?因为它的优点如下:

  • 三角形是最基础的多边形,任何其他不同的多边形都可以拆成若干个三角形。
  • 我们可以通过向量的叉积来判断一个点是在三角形内或者外,但是对于有凹凸的多边形不行。
  • 我们可以给定三个顶点不同的属性,在三角形内做出渐变效果,即可根据插值算出三角形内任意一点的属性。

 

单个三角形光栅化

通过上面的解释,我们又把问题进行了简单化,也就是把三维物体光栅化即是把无数个三角形进行光栅化。那么同样由繁化简,我们先来看看如何把一个xy平面上的三角形(忽视z轴)进行光栅化,如下图,背景中的黑色实线所围成的小格子即是我们的像素,灰色虚线为辅助线,方便看像素的中心点:

图形的光栅化_简述图像的采样和量化过程

 

首先可以肯定的是,光栅化后,肯定不是像上图那样的显示了。因为前面我们说过一个像素中只会存在一个颜色,而上图明显不符合这个要求,例如我们看下标为(1, 2)的像素点,里面只有一部分是红色的。那么这个像素到底应该是没有颜色还是全部红色呢?在图形学中,我们定义若一个像素的中心点在三角形的内部,那么这个像素就属于该三角形。例如例子中下标为(1, 2)的像素点,我们可以从图中明确的看出其中心点在三角形内部,那么这个像素就应该全部显示红色。

当然了,我们肯定不可能通过肉眼来观察是否在三角形内部,因此光栅化过程中很重要的一步便是:判断像素的中心点与三角形的内外关系。这里也就体现了使用三角形的好处,因为前面我们说了使用叉积的方法可以判断点和三角形的内外关系(原理就不多赘述了,不了解的可以查看之前的文章的叉积部分)。那么我们就可以定义一个函数用来判断,如下:

bool isInside(t, x, y){}

函数体内即使用叉积来判断,若在三角形内则返回true,不在则返回false。输入的参数 t 代表三角形的信息集合(三个顶点的x,y信息),输入的参数 x 和 y 即点的位置信息。

此处还存在一个问题,即点正好在三角形的边上,那么我们应该如何考虑,到底是算还是不算在三角形内部呢。至于这个问题,就全看使用者自己的定义了,我们可以定义算在也可以不算,后续的操作只需要遵从自己的定义即可。在本章中,我们认为这种情况不在三角形内。

知道了屏幕中任何一个点和三角形的关系后,我们遍历每个像素,取其中心点的x和y带入该函数中即可,而这步操作,我们称之为采样。

 

采样(sampling)

何为采样?是指从总体中抽取个体或样品的过程。用程序的思维来解释的话,就是给定一个连续的函数,然后我们通过不同的输入来获取函数的值。例如有个函数 y=f(x) ,我们分别求 x = 1,x = 2,x = 3…时y的值。采样即把一个函数给离散化(Discretize)的过程,在图形学中有广泛的应用。

采样频率:采样频率可以理解为抽取样本的间隔,例如上面我们采样的是x = 1,x = 2,x = 3…,间隔为1。如果改成x = 1,x = 3,x = 5… ,间隔为2,那么就代表频率变慢。而改成x = 0.5,x = 1,x = 1.5… ,间隔为0.5,那么就代表频率变快。

应用到我们现在所说的屏幕中的话,我们前面已经把摄像机所观测的空间通过一系列变换,变成了在xy方向上和屏幕一样大的空间了。而这个空间内的可以想象成是由无数个连续的点所组成(忽略z)。而我们的屏幕又由一个个的像素组成,这些像素的中心点对应到空间中的点,就是我们从空间中所有点中所抽取出的样本。那么采样的间隔自然是我们像素的实际物理大小。因此若屏幕大小不变,分辨率越高(即像素越多,像素的中心点物理间隔越小),采样的频率越高。

上面介绍的属于采样二维空间中的位置信息,此外我们还可以采样时间,例如下图,便是采样了不同时间人挥球的动作,进行了显示

图形的光栅化_简述图像的采样和量化过程

前面我们说了屏幕是由 width * height 个像素点组成的,因此要采样每个像素的中心点,只需要遍历所有像素,然后将其中心点的x和y带入上面定义好的函数中即可:

for(int x = 0; x < width; x++){
    for(int y = 0; y < height; y++){
        pixel[x][y] = isInside(t, x + 0.5, y + 0.5);//前面提到像素中心点是像素坐标x,y的值+0.5
    }
}

这样我们就可以知道在三角形内的所有像素了,如下图,顶点标记黑色的即为在三角形内的顶点。

图形的光栅化_简述图像的采样和量化过程

思考:如果我们修改下上面的代码,改成如下,那是做了什么事情?

for(int x = 0; x < width; x++){
    for(int y = 0; y < height; y++){
        pixel1[x][y] = isInside(t, x + 0.25, y + 0.25);
        pixel2[x][y] = isInside(t, x + 0.25, y + 0.75);
        pixel3[x][y] = isInside(t, x + 0.75, y + 0.25);
        pixel4[x][y] = isInside(t, x + 0.75, y + 0.75);
    }
}

其实很简单,做的就不再是采样每个像素的中心点,而是讲一个像素分成了如下图的四块,然后采样每个像素这四块的中心点。

图形的光栅化_简述图像的采样和量化过程

上诉就是MSAA的核心思想,在下一篇文章中会介绍到。

 

Bounding Box

在上面的采样中,我们需要采样屏幕中所有的像素中心点,但是实际上我们的三角形可能非常的小,就占了几个像素,那么这种做法就会造成很大的性能消耗。因此我们可以使用Bounding Box来缩小我们的采样范围。例如下图,可能在三角形内的像素肯定是在蓝色区域的范围内,而这个蓝色区域我们就称之为Bounding Box。也可称之为轴向的包围盒,即Aixe align bounding box,也就是常说的AABB。

图形的光栅化_简述图像的采样和量化过程

因此,给定三角形的三个顶点,我们只需要求出三个顶点的在x轴的最大最小值,在y轴的最大最小值,即可定义出一个Bounding Box,然后只需要在这个Bounding Box中进行采样即可。

for(int x = xmin; x < xmax; x++){
    for(int y = ymin; y < ymax; y++){
        pixel[x][y] = isInside(t, x + 0.5, y + 0.5);
    }
}

这样我们已经可以减少很多的采样了,但是还有些特殊的情况,导致三角形本身依旧不大,但是Bounding Box特别大,例如下图这种情况:

图形的光栅化_简述图像的采样和量化过程

针对这种情况,我们也可做特殊处理,例如每行做一个Bounding Box,然后从左到右遍历,如下。

图形的光栅化_简述图像的采样和量化过程

至于怎么判断每行的Bounding Box的左右边界,以及怎么判断这个三角形属于这种特殊情况,等后续的学习再进行补充。

个人的思路(对不对么,我也不知道)是:

1.从图中我们可以看出只有在三角形瘦长且倾斜的时候会导致这样的情况发生,那么我们需要去判断一个三角形是否瘦长且倾斜?其实并不需要这么麻烦,我们只需要计算三角形的面积 S_{tri} 与其所占的Bounding Box的面积 S_{bb} 的占比即可,因为需要特殊处理情况,肯定是 S_{tri}/S_{bb} 特别小的情况(可以假设小于0.3需要特殊处理)。

2.S_{bb} 的面积很好求,宽乘高即可。S_{tri} 面积我们需要用到海伦公式,因为我们知道三角形的三个顶点位置,也就可以求出三条边的边长,通过海伦公式即可求出三角形的面积。设三条边的边长分别为a,b,c,则 S_{tri}=\sqrt{p(p-a)(p-b)(p-c)} 其中 p 为半周长,即 p=\frac{a+b+c}{2} 。

3.接着就是怎么逐行定义Bounding Box,因为是逐行的所以每行的Bounding Box的ymin和ymax很清楚的可以知道,问题就在于xmin和xmax的值了。

4.我们来单独看下某一行,如下图:

图形的光栅化_简述图像的采样和量化过程

想要确定改行的Bounding Box宽度,我们只需要求出图中标记的四个点的x值(y值已经可以确定),然后求出最大和最小的x即可。从图中我们可以看出,这四个点分别在三角形的某两条边上。(若是像上图中的最下面一行的特殊情况,看着只有三个点,我们可以想象成最下面的那个点是两个点重叠即可)那么我们就要确定这两条边是三角形的哪两条边。由于四个点的y值是确定的(该行的ymin和ymax),那么肯定是一个y值大于ymin和ymax的顶点和y值小于ymin和ymax的顶点的连线。(不可能三角形三个顶点都大于或都小于ymin和ymax)

确定了四个点所在的两条边之后,我们的问题就等于变成了求直线上的一点,知道了直线的两个顶点的值,和直线中一点的y值,可以很轻松的求出该点的x的值。例如上图中标记的左下角点,其y值为ymin,设其所在边的两个顶点分别为(x1, y1) 和 (x2, y2) ,其中 x1<x2,y1<ymin<y2,那么 该点的x值为: x=\frac{x2-x1}{y2-y1}(ymin-y1)+x1 ,其他点的值同理。

 

结果

通过上面的知识,我们可以找到屏幕中在三角形内部的像素,接着我们对这些像素进行着色,就可得到如下结果

图形的光栅化_简述图像的采样和量化过程

上图也就是我们单个三角形进行光栅化后的结果,也就是屏幕中真正显示的样子。

很显然,这个效果看着和原本的三角形差距很大,三角形的边缘处都是凹凸不平的,也就是所谓的锯齿。因此在下篇文章中,我们要学习如何来抗锯齿,使其看起来更像三角形一些。

 

所有物体光栅化

前面我们说的是一个xy平面上的三角光栅化,那么对于xyz空间中的三角形光栅化呢?其实是一样的,我们只要抛弃z轴的值,使其变成xy平面上的三角形即可。专业的说,把空间xyz中的三角形投影到xy屏幕上。例如在正交投影中,如果三角形三个顶点的y值都相等,那么我们看见的就会是一条直线。

但是我们会发现,在透视投影中,如果三角形三个顶点的y值都相等,看见的并不是一条直线啊,而是能看出三角形的形状。别忘了,我们在做透视投影时,会先做一步将视锥体压缩成长方体的操作,在这个操作完成后,原本三角形三个顶点相等的y值就会变得不相等了。

我们知道一幅画面是由多个物体的表面所构成的,例如地面的表面,人的表面,房子的表面等等。而每个表面(也称之为Mesh)又是由多个大小形状不一的三角形组成。那么只需要遍历画面中所有的Mesh,然后再遍历这些Mesh中的三角形,通过一个个三角形光栅化,我们就可以将视口变换后的整个空间绘制到屏幕上了。

那么有个问题,我们知道我们看向不同的物体的时候,它们之间可能存在前后重叠/遮挡的关系,例如背着书包的人,正面看去书包和人是重叠的。并且对于单个三维物体而言,不同的面也是存在重叠关系的,例如书包的背面和正面。换句话说,所有要绘制的三角形,它们可能存在着重叠的关系,对于这些重叠的三角形,我们应该把谁显示在像素上?

 

深度

物体能够重叠,说明他们间存在着前后关系。对于空间中物体的前后位置,更专业的术语称之为深度,我们用 表示,范围为 0.00 ~ 1.00。z值越大,即深度越深,代表离摄像机越远。(注意这个z值和坐标系的z值不一样,若按坐标系的话,因为视图变换后摄像机看向-z,也就是说z越小,离得越远)

对于正交投影,深度的计算方式如下:

z_{depth}=\frac{z_{coord}-near}{far-near}

z_{depth} 即为深度,z_{coord} 即为物体在z轴的值。near 和 far 分别代表近平面和远平面的z轴的值。

因为正交投影变换没有对空间扭曲,四边形变换后还是四边形,所以在正交投影变换前后都可以使用该式子去计算深度。做完正交投影变换后 near = -1,far = 1。

正交投影的深度为线性增长,如下图:

图形的光栅化_简述图像的采样和量化过程

可以发现在正交投影中,深度为0.5即代表在空间中z轴方向的中间。

但是透视投影又不一样了,其深度的计算方式如下:

z_{depth}=\frac{\frac{1}{z_{coord}}-\frac{1}{near}}{\frac{1}{far}-\frac{1}{near}}

透视投影的深度为非线性增长,其曲线如下图:

图形的光栅化_简述图像的采样和量化过程

从中可以看出当深度为0.5时,并不代表在空间中z轴方向的中间。这也印证了在透视投影变换时,我们将透视的视锥体变换成正交的长方体时(操作一),中心点的坐标系z值是发生变换的。在操作一前,我们透视投影要使用该式子计算深度,但是操作一完成后,计算深度就要使用正交投影对应的式子了。

 

画家算法(Painter’s Algorithm)

在生活中,我们知道当两个物体发生重叠,我们只能看到离我们更近的那个物体。同样的,对于摄像机而言,我们也只显示深度更小的那一个。

这样我们是不是只需要求出所有要光栅化的三角形的深度,然后将它们按深度排序,从深度深的三角形开始光栅化即可呢?

对于上面的操作,我们称之为画家算法,因为要按深度排序,因此对于n个三角形,时间复杂度为 O(n \log_n) 。看着似乎是可行的,例如我们先光栅化背包背面的三角形(深度高)显示在像素上,然后光栅化正面的三角形(深度低),这样像素上就会覆盖了原本背面的颜色,没有什么问题。

但是如果是下面这两种种情况呢?

情况1:如下图,两个面的z值相同,即深度相同,那么为什么我们看见的重叠部分是红色在后白色在前呢?

图形的光栅化_简述图像的采样和量化过程

情况2:如下图,三个三角形是互相重叠的关系,无论是P-R-Q还是Q-R-P等顺序去光栅化,都不能达到我们图中的效果。

图形的光栅化_简述图像的采样和量化过程

上面的例子都说明画家算法在一些特殊的情况是不可行的。

 

深度缓存(Z-Buffer)

前面的例子推翻了我们使用每个三角形的深度做光栅化的操作,因此为了解决类似上面这也的问题,图形学中引入了一个新的算法,叫深度缓存。

什么是深度缓存呢,字面意思上似乎是把深度值缓存起来,实际上也确实是这样,但是这里的深度不再是每个三角形的深度,而是针对每个像素来处理。例如下图:

图形的光栅化_简述图像的采样和量化过程

依旧是之前的例子,假设图中的小红块代表着一个像素,那么在这个像素中,R的深度肯定是小于P的深度的,我们假设在这个像素中R的深度为0.3,P的深度为0.5,然后我们会有个值用来存储这个像素对应的深度信息(默认值设为正无穷)。

此时我们就不用管绘制顺序了,例如先绘制P,绘制到该像素时,先对比P在该像素的深度(0.5)和已存入的深度的大小(由于之前没有存过所以是默认值),0.5<正无穷,因此这个像素显示P的颜色,并存入深度值0.5。然后我们绘制R,对比R在该像素的深度(0.3)和已存入的深度的大小(0.5),0.3<0.5,更新像素颜色,显示R的颜色,并更新深度值为0.3。反之亦然,我们先绘制R,0.3<正无穷,显示R的颜色,存入0.3。然后绘制P,0.5>0.3,因为深度更大的不用显示,因此就不用管了。这样就可以解决我们上面提到的画家算法没法解决的问题了。

对于上面的情况1也是一样的,例如下图中,依旧以红色为像素块,那么在该像素中白色的深度是小于红色的,因此显示白色。

图形的光栅化_简述图像的采样和量化过程

可见两者的区别在于:

  • 画家算法中,一个三角形只有一个深度值,及其重心点的深度
  • 深度缓存算法中,一个三角形根据它所占用的像素,拥有多个深度值,每个像素对应一个深度。

因此在深度缓存算法中,我们会有两个buffer,如下:

  • frame buffer:用来存储每个像素的颜色值
  • z-buffer(depth buffer):用来存储每个像素所对应的深度值,只保存值最小的那一个,默认值为正无穷。

深度算法简单的逻辑代码如下:

float[,] frameBuffer = new float[width, height];//存储每个像素的最终颜色
float[,] zBuffer = new float[width, height];//存储每个像素的深度值
for (int x = 0; x < width; x++)
{
    for (int y = 0; y < height; y++)
    {
        //设默认值为正无穷
        zBuffer[x, y] = float.PositiveInfinity;
    }
}
//遍历所有三角形
foreach (Triangle t in allTriangle)
{
    //光栅化每个三角形
    for (int x = xmin; x < xmax; x++)
    {
        for (int y = ymin; y < ymax; y++)
        {
            //如果该像素在该三角形里
            if (isInside(t, x + 0.5, y + 0.5))
            {
                //比较该三角形在这个像素的深度和已经保存了的深度
                if (t.z < zBuffer[x, y])
                {
                    //如果新的深度值更小,则更新颜色和深度值
                    frameBuffer[x, y] = t.color;
                    zBuffer[x, y] = t.z;
                }
                else
                    ;//反之不用任何操作
            }
        }
    }
}

对于深度缓存算法,其时间复杂度为 O(n),因为它并不是一个排序操作(排序的时间复杂度最小也是 O(n \log_n) ),它仅仅只是求一个最小值,而不需要知道除了最小值外其他值的顺序如何。

了解了这些后,我们看下面这个例子就很清楚了:

图形的光栅化_简述图像的采样和量化过程

图中每个格子代表一个像素,先后光栅化了一红一蓝两个三角形,根据不同的深度值得到的最终结果。


因为两个buffer的大小都是像素在屏幕上的宽和高的数量,因此这两个buffer我们都可以得到一幅图像,例如下图:

图形的光栅化_简述图像的采样和量化过程

frame buffer对应的自然就是最终渲染出来的图像,而depth buffer对应的图像我们称之为深度图。

在深度图中越黑的代表越近,因为越近就是深度越小,即越接近于0,而在RGB颜色中,0即代表着黑色。反之越远,即深度越接近于1,即白色。这样就很容易看懂右边这幅深度图了。

其他一些问题:

问题一,虽然我们深度值是浮点型,但是还是可能存在相等的情况,那么若碰见深度值相同的情况,该如何显示?这里可能就需要我们特殊处理了

问题二,对于带有透明度的物体,深度缓存的方法是无法处理的。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/179164.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • javaSocket传输模式

    javaSocket传输模式Sockets有两种主要的操作方式:面向连接的和无连接的.面向连接的sockets操作就像一部电话,他们必须建立一个连接和一人呼叫.所有的事情在到达时的顺序与它们出发时的顺序时一样.无连接的sockets操作就像是一个邮件投递,,没有什么保证,多个邮件可能在到达时的顺序与出发时的顺序不一样.到底用哪种模式是邮应用程序的需要决定的.如果可靠性更重要的话,用面向连接的操作会好一些.比如文件服务器需要他们

  • mysql——cmd进入mysql及常用的mysql操作[通俗易懂]

    mysql——cmd进入mysql及常用的mysql操作[通俗易懂]cmd进入mysql操作win+R,输入cmd,打开cmd窗口,进入到mysqlbin目录的路径下第一步:启动mysql服务,可以通过“netstartmyql”命令实现;第二步:先使用DOS命令进入mysql的安装目录下的bin目录中;第三步:在命令行输入:mysql-u用户名-p密码;回车;-h表示服务器名,localhost表示本地,-hlocalhost可不输入;-u为数据库用户名,root是mysql默认用户名;-p为密码,如果设置了密码,可直接在-p后链接输入,如

  • Latex 参考文献格式

    Latex 参考文献格式在Latex中,一般使用.bib文件,维护一个参考文献库,对于中英文要求文后的参考文献显示格式不同,我们仅需要修改.tex文件中的引用格式即可。一.对于中文文章,参考文献格式一般要求按照下面的格式进行排版显示格式一般为(举个例子):在Latex中,我们仅需要修改两处:1.在\begin{document}前面加上\usepackage[numbers]{gbt7714}2.在后面参考文献处写上:{\small \bibliographystyle{gbt7714-nume

  • python中%d_python中%d是什么「建议收藏」

    python中%d表示格式化一个对象为十进制整数。使用后,在需要输出的长字符串中占位置。输出字符串时,可以依据变量的值,自动更新字符串的内容。使用示例:num=14#%d打印时结果是14print(“num=%d”%num)#output:num=14#%1d意思是打印结果为1位整数,当整数的位数超过1位时,按整数原值打印,所以%1d的打印结果还是14print(“nu…

  • iReport 分组统计[通俗易懂]

    iReport 分组统计[通俗易懂]参考http://jaspereport.group.iteye.com/group/wiki/3401-jaspereport-ireport做出了分组统计的效果。1、在报表中新建一个group2、新建一个变量,设置属性3、在报表中插入一个TextField,设置相关分组属性。

  • H5请在微信客户端打开链接「建议收藏」

    H5请在微信客户端打开链接「建议收藏」H5判断必须在微信中打开<!DOCTYPEhtml><html><head><metacharset=”utf-8″><title></title></head><body></body><scripttype=”text/j…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号