OpenGL ES像素着色器

目 录

  1. 准备开始 
  2. 像素着色器 vs 顶点/片段着色器 
  3. 像素着色器101:渐变 
  4. 像素着色器几何学 
  5. 像素着色器程序生成纹理:Perlin噪声 
  6. 像素着色器绘制的月球 
  7. 何去何从? 

原文地址:http://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial 泰然翻译组:cissyhope。校对:蓝羽。 

在这个像素着色器(pixel shaders)教程里,你将学到如何把你的iPhone变成一块全屏的GPU画板。

这意味着你要做一个底层的图形密集型的app,通过有意思的数学公式在屏幕上把每个像素画出来。

为什么要研究这个呢?因为像素着色器除了是计算机图形学里最酷的东西,还在很多领域非常有用:

  • 程序生成复杂背景
  • 现场视频色调抠像
  • 音乐可视化
<code>注意:上面的demo是用WebGL实现的,只有Chrome和Opera能完美支持,至少在写本教程的时候是这样。而且它们都比较耗性能——所以尽量不要一次打开多个tabs同时跑。

你要写的着色器没有上面的这么复杂,不过要是你熟悉OpenGL ES,就能通过这些练习获得更多。如果你是第一次和这些API打交道,那么请先看看我们在这个主题上的 书面 或者 视频 教程。 

不要再耽搁了,让我开始荣幸的为你介绍iOS里的像素着色器吧!

<code>注意:教程里提到的“图形密集型(graphics-intensive)”不是在开玩笑。这个app很容易就会让你的iPhone的GPU跑到极限,所以请使用iPhone5或者更高版本。如果你没有这么新的设备,就用iOS模拟器也可以。

准备开始

首先,下载本教程的 起始包 。去 RWTViewController.m 文件可以看到 GLKViewController 的轻量实现,然后编译运行。你的屏幕应该像下面看到的这样: 

还没什么特别的地方,但我肯定绿巨人会喜欢。

本教程里,全绿的屏幕代表了你的基础着色器( RWTBase.vsh 和 RWTBase.fsh )在正常工作,而你的OpenGL ES代码也都设置正常。在整个教程中,绿色就代表“前进”,红色代表“停下”。 

如果任何时候你发现你正盯着一个全红的屏幕,你应该“停下”并验证你的代码,因为你的着色器编译失败,没能正确链接。这套机制是通过在RWTViewController的 viewDidLoad 方法里将 glClearColor() 设成红色来实现的。 

快速的过一下 RWTBase.vsh ,这会是你能遇到的最简单的顶点着色器之一。它唯一做的事情就是根据aPosition在x-y平面计算一个点。 

顶点属性数组 aPosition 是通过 RWTBaseShader.m 的 RWTBaseShaderQuad 传入的,这是一个四元组,存放屏幕的四个顶点(根据OpenGL ES坐标系)。 RWTBase.fsh 是一个更加简单的片段着色器,将所有的片段不分位置的设为绿色。这就是为什么你会看到一个全绿的屏幕! 

现在,让我们更深入一点。。。

像素着色器 vs 顶点/片段着色器

如果你看过一些我们之前的OpenGL ES教程,你可能注意到了我们说顶点着色器是控制顶点的,而片段着色器是控制片段的。基本上,顶点着色器用于画出对象,而片段着色器用于给他们上色。片段着色器可能会生成像素也可能不会,这取决于很多因素,比如深度,透明度以及视口坐标。

所以,如果你渲染根据如下所示四个顶点确定的四元组,会看到什么呢?

假定你没有打开透明度混合或者深度测试,你会看到一个不透明的全屏 直角平面 。 

在这些条件下,经过图元光栅化后,每个片段刚好对应了屏幕上的一个像素——不多也不少。因此这个片段着色器会直接对屏幕的每个像素着色,所以被称为像素着色器。

<code>注意:**GL_BLEND****GL_DEPTH_TEST**默认是禁用的。你可以在[这里](http://www.khronos.org/opengles/sdk/1.1/docs/man/glEnable.xml)查看**glEnable()****glDisable()**都能做些什么,也可以在代码中通过函数**glIsEnabled()**进行查询。

像素着色器101:渐变

你的第一个像素着色器是一个计算线性渐变的简单课程。

<code>注意:为了节省篇幅,将重点放在本教程的算法和方程上,**floats**的全局GLSL **precision(精度)**值被定义为**highp**。官方的[OpenGL ES iOS编程指南](https://developer.apple.com/library/ios/documentation/3ddrawing/conceptual/opengles_programmingguide/BestPracticesforShaders/BestPracticesforShaders.html)里有一小节介绍了应该如何选择精度,还有一篇[iOS设备兼容性说明](https://developer.apple.com/library/ios/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/OpenGLESPlatforms/OpenGLESPlatforms.html),当你之后想做优化时可以参考。记住,对于iPhone5全屏画面而言,每个片段着色器每帧都会被调用**727,040**640*1136)次!

像素着色器背后的魔法就藏在 gl_FragCoord 里。这个片段独有的变量存放了当前片段相对窗口的坐标。 

对于一个普通的片段着色器, “这个值是从顶点生成片段之后对图元进行固定函数插值的结果”。 对于像素着色器而言,你只需要知道这个变量的 xy 值刚好唯一对应屏幕上的一个像素。 

打开 RWTGradient.fsh ,在 precision 的下方加上: 

<code>// Uniforms
uniform vec2 uResolution;

uResolution 来自 RWTViewController.m 中 glkView:drawInRect: 的 rect 变量(也就是包含你视图的矩形)。 

RWTBaseShader.m 里的 uResolution 用于处理 rect 的宽高,并在 renderInRect:atTime: 方法里将它们赋值给了对应的GLSL常量。也就是说 uResolution 存放了你屏幕的x-y分辨率。 

你可以通过除法 gl_FragCoord.xy/uResolution 来把你的像素坐标转化到 0.0 ≤ xy ≤ 1.0 这个范围,这在很多时候能大大简化像素着色器的计算。这也是 gl_FragColor 的绝佳取值范围,现在让我们来看一些渐变! 

在 RWTGradient.fsh 的 main(void) 里增加如下几行: 

<code>vec2 position = gl_FragCoord.xy/uResolution;
float gradient = position.x;
gl_FragColor = vec4(0., gradient, 0., 1.);

然后把程序用的片段着色器从 RWTBase 变到 RWTGradient ,在 RWTViewController.m 里将以下代码: 

<code>self.shader = [[RWTBaseShader alloc] initWithVertexShader:@"RWTBase" fragmentShader:@"RWTBase"];

改为:

<code>self.shader = [[RWTBaseShader alloc] initWithVertexShader:@"RWTBase" fragmentShader:@"RWTGradient"];

编译运行!你的屏幕会从左到右显示很棒的由黑变绿的渐变效果:

很酷是不是?要想让画面从下到上渐变的话,将 RWTGradient.fsh 里的 

<code>float gradient = position.x;

改成:

<code>float gradient = position.y;

再次编译运行就能看到在新方向的渐变。。。

现在给你个挑战!能在着色器里只改一行代码来实现如下截图效果吗?

提示: 记住 position 和 gl_FragColor 的取值范围都是从 0.0到1.0。 

对角渐变的答案是:

<code>float gradient = (position.x+position.y)/2.;

如果你做对了,恭喜你!如果没有,那请先复习一下本段再继续往下看。:]

像素着色器几何学

在本段中,你将学到如何利用数学知识绘制简单图形,我们将从2D的圆盘圆环开始,到3D球体结束。

几何学:2D圆盘

打开 RWTSphere.fsh ,在 precision 下方加入以下代码: 

<code>// Uniforms
uniform vec2 uResolution;

增加的是和前一段落里相同的常量,已经可以满足你生成静态几何图形的要求。要创建一个圆盘,在 main(void) 里增加如下代码: 

<code>// 1
vec2 center = vec2(uResolution.x/2., uResolution.y/2.);// 2
float radius = uResolution.x/2.;// 3
vec2 position = gl_FragCoord.xy - center;// 4
if (length(position) > radius) {gl_FragColor = vec4(vec3(0.), 1.);
} else {gl_FragColor = vec4(vec3(1.), 1.);
}

这儿用到了一些数学知识,来看看究竟发生了什么:

  1. 圆盘的 center 会处于你屏幕的正中央。 
  2. 圆盘的 radius 会是你屏幕宽度的一半。 
  3. position 是当前像素的坐标相对圆盘圆心的偏移值。可以想象成是一个向量从圆盘圆心指向当前位置。 
  4. length() 用于计算向量长度,在这个例子里长度是根据勾股定理 √(position.x²+position.y²) 来计算的。 

    A.如果结果比 radius 大,说明当前像素在圆盘区域外,那么就染成黑色。 

    B.否则,说明当前像素在圆盘区域内,染成白色。

    作为对这个行为的补充说明,可以参看 圆形方程 (x-a)²+(y-b)² = r² 。注意 r 是半径, ab 是圆心,而 xy 是圆上所有点集。 

圆盘是平面上被圆形围住的一块区域,上面的 if-else 语句会准确的把它画出来! 

在你编译运行之前,在 RWTViewController.m 里把程序的片段着色器改成 RWTSphere : 

<code>self.shader = [[RWTBaseShader alloc] initWithVertexShader:@"RWTBase" fragmentShader:@"RWTSphere"];

现在编译运行。你的屏幕应该显示一个在黑色背景上的实心白色圆盘。这并不是最创新的设计,但却是我们的起点。

你可以根据圆盘的属性随意修改代码,看看会对渲染结果产生什么影响。作为额外的挑战,试试能不能画出如下圆环呢:

提示: 试着根据 radius 创建一个叫 thickness 的新变量,并在 if-else 条件中使用。 

细圆环的解决方案是:

<code>vec2 center = vec2(uResolution.x/2., uResolution.y/2.);
float radius = uResolution.x/2.;
vec2 position = gl_FragCoord.xy - center;
float thickness = radius/50.;if ((length(position) > radius) || (length(position) < radius-thickness))   gl_FragColor = vec4(vec3(0.), 1.);
} else {gl_FragColor = vec4(vec3(1.), 1.);
}

如果你刚刚尝试了这个挑战,或者修改了GLSL代码,现在请改回显示白色实心圆盘的代码(同时为你的好奇心喝彩)。

用以下代码替换你的 if-else 条件: 

<code>if (length(position) > radius) {discard;
}gl_FragColor = vec4(vec3(1.), 1.);

亲爱的读者,请允许我为你介绍 discard 。这是一个片段独有的关键字,告诉OpenGL ES丢弃当前片段,并在渲染管道后面的阶段中也忽略它。编译运行可以看到以下画面: 

在像素着色器的术语里, diacard 返回一个不写到屏幕的空白像素。因此 glClearColor() 决定了屏幕这个位置绘制的内容。 

从这里开始,如果你看到亮红色的像素,说明 discard 正在正常工作。但还是要注意全屏红色的情况,这说明代码中有错误。 

几何学:3D球体

现在是时候来点新玩意将单调的2D圆盘转换成3D球体了,要做到这一点你需要引入深度。

在一个典型的顶点+片段着色器程序中,这可能很简单。顶点着色器可以处理3D几何输入,并将其和其他必要信息一起传给片段着色器。但是,对于像素着色器而言,你只有一个2D平面可供“绘制”,所以需要 通过推导z值来模拟深度 。 

在几段内容之前,你通过将圆内所有像素着色创建了一个圆盘,这个圆是由以下方程定义的:

(x-a)²+(y-b)² = r²

要将它扩展成 球体方程 非常简单,就像这样: 

(x-a)²+(y-b)²+(z-c)² = r²

c 是球体在 z 轴上的中心。既然你是在圆心 ab 的位置建立的2D坐标系,而你的新球体中心又将处在 z 轴的原点上,那么这个方程可以简化成: 

x²+y²+z² = r²

根据方程解出 z 来: 

z² = √(r²-x²-y²)

这样你就根据它们各自独一无二的位置,为所有的片段推推出了 z 值!足够幸运的是,在GLSL中这很容易通过代码计算。将如下代码加到 RWTSphere.fsh 中,就放在 gl_FragColor 的前面。 

<code>float z = sqrt(radius*radius - position.x*position.x - position.y*position.y);
z /= radius;

第一行按照前面推导的方程计算 z ,第二行将这个值跟球体 radius 做除法,使得取值范围变到 0.0 到 1.0 之间。 

为了将球体深度可视化,将当前 gl_FragColor 一行改为: 

<code>gl_FragColor = vec4(vec3(z), 1.);

编译运行就可以看到原本平面的圆盘现在多了第三维的效果。

因为z轴正方向是从屏幕向外指向观测者的,球体离我们最近的部分是白色(中心),而最远的部分是黑色(边缘)。

自然的,在两者中间的点形成了平滑灰色的渐变。这段代码最快速简单的可视化了深度,但是忽略了球体的xy值。如果这个图形旋转起来,或者和其他对象放在一起,你没法分辨它的上下左右。

将这行:

<code>z /= radius;

替换为:

<code>vec3 normal = normalize(vec3(position.x, position.y, z));

引入 法线(normals) 的概念,可以将方位也在3D空间可视化。在这个例子中,法线是与你的球体表面垂直的向量。对于任意一点,法线确定了这一点朝向的方向。 

在这个球体的例子里,为每个点计算法线是很简单的。我们已经有了向量( position )从球体的圆心指向当前点,也知道它的z值。这个向量的方向和点的朝向,也就是法线方向是一致的。 

如果你学习过我们以前的OpenGL ES教程,就知道为了简化后续计算(特别是光照),我们通常会 normalize() 向量。 

被归一化(normalized)后,它的取值范围会落在 -1.0 ≤ n ≤ 1.0 ,而像素颜色通道的取值范围是 0.0 ≤ c ≤ 1.0 。为了更好的将你球体的法线可视化,我们从 n 向 c 作如下转换: 

<code>-1.0 ≤ n ≤ 1.0
(-1.0+1.0) ≤ (n+1.0) ≤ (1.0+1.0)
0.0 ≤ (n+1.0) ≤ 2.0
0.0/2.0 ≤ (n+1.0)/2.0 ≤ 2.0/2.0
0.0 ≤ (n+1.0)/2.0 ≤ 1.0
0.0 ≤ c ≤ 1.0
c = (n+1.0)/2.0

好啦!就这么简单。现在将这一行:

<code>gl_FragColor = vec4(vec3(z), 1.);

替换成:

<code>gl_FragColor = vec4((normal+1.)/2., 1.);

然后编译运行。准备好迎接圆形彩虹的视觉盛宴:

可能第一眼看上去比较晕,特别是和前一个平滑球体比起来,但是在这些颜色里隐藏了很多有价值的信息。。。

你现在看到的其实是球体的 法线图(normal map) 。在法线图里,rgb的颜色代表了表面法线实际的xyz坐标。见下图: 

圈出来的点的rgb值分别为:

<code>p0c = (0.50, 0.50, 1.00)
p1c = (0.50, 1.00, 0.53)
p2c = (1.00, 0.50, 0.53)
p3c = (0.50, 0.00, 0.53)
p4c = (0.00, 0.50, 0.53)

之前你从法线向量 n 转换得到了颜色 c 。现在利用逆推公式 n = (c*2.0)-1.0 ,这些颜色又能映射回特定的法线: 

<code>p0n = (0.00, 0.00, 1.00)
p1n = (0.00, 1.00, 0.06)
p2n = (1.00, 0.00, 0.06)
p3n = (0.00, -1.00, 0.06)
p4n = (-1.00, 0.00, 0.06)

如果用箭头表示,看起来大概像这样:

现在,对于你的球体在3D空间的朝向应该再没有歧义了。更进一步,现在还能给对象加上合适的光照!

在 RWTSphere.fsh 的 main(void) 里加上以下代码: 

<code>// 常量
const vec3 cLight = normalize(vec3(.5, .5, 1.));

这个常量定义了将照亮你的球体的虚拟光源的朝向。在这个例子里,光源从右上角射向屏幕。

然后将以下代码:

<code>gl_FragColor = vec4((normal+1.)/2., 1.);

替换成:

<code>float diffuse = max(0., dot(normal, cLight));gl_FragColor = vec4(vec3(diffuse), 1.);

你可能看出来了,这是 Phong反射模型 中 漫反射(diffuse) 成分的简化版。编译运行就可以看到被很好的照亮的球体! 

<code>注意:如果你想了解更多关于Phong光照模型的知识,可参考[环境光(Ambient)](http://www.raywenderlich.com/70532/video-tutorial-beginner-opengl-es-glkit-part-7-ambient-lighting),[漫反射(Diffuse)](http://www.raywenderlich.com/70548/video-tutorial-beginner-opengl-es-glkit-part-8-diffuse-lighting),[镜面反射(Specular)](http://www.raywenderlich.com/70648/video-tutorial-beginner-opengl-es-glkit-part-9-specular-lighting)的视频教程【仅对订阅者开放】。

在二维画布绘制三维对象?只使用数学知识?一个像素一个像素的画?哇你做到了!

现在是时候小小的休息一下,好让你沐浴在胜利的光辉中。。。同时也清空你的大脑,因为亲爱的读者,你才刚上路呢。

像素着色器程序生成纹理:Perlin噪声

在这个部分你将学到的知识包括:纹理图元,伪随机数生成器以及基于时间的函数——最后通过它们你能实现一个基本的噪声着色器,其灵感来源于 Perlin噪声 。 

Perlin噪声背后的数学知识对这篇教程而言可能太深了一点,而且完整的实现因为过于复杂也很难跑到30帧。

例子里这个着色器虽然基本,但也还是会涵盖各种噪声的基础知识(在此特别鸣谢 Hugo Elias 和 Toby Schachman 提供的模块解释/示例)。 

Ken Perlin在1981年为电影 TRON 设计了Perlin噪声,这是计算机图形学发展历史上,最具开创性的基础算法之一。 

它可以模拟自然元素中的伪随机模式,比如云彩和火焰。它在现代CGI中是如此的无处不在,以至于Ken Perlin最后因为这项技术,及其对电影工业的贡献而获得了 奥斯卡技术成就奖 。 

奖项本身就很好的解释了Perlin噪声的要点:

“颁给设计了Perlin噪声的Ken Perlin,这项技术被用在电影特效中,在计算机生成的表面上显示自然纹理。Perlin噪声的出现,帮助了计算机图形学艺术家更好的在电影工业特效中模拟复杂的自然现象。”

云:噪声在x轴和z轴上变换

火焰:噪声在x轴和y轴缩放,在z轴变换

是的,这看起来很复杂。。。但是你将从最基础的部分开始实现。

首先你要熟悉时间输入以及数学函数。

程序化纹理:时间

打开 RWTNoise.fsh ,在 precision highp float; 下面增加以下代码: 

<code>// Uniforms
uniform vec2 uResolution;
uniform float uTime;

你应该已经很熟悉 uResolution 了,但是 uTime 还是第一次见到。 uTime 来自 GLKViewController 的派生类,也就是 RWTViewController.m 的 timeSinceFirstResume 属性(即:从视图控制器首次恢复更新事件以来流逝的时间)。 

uTime 在 RWTBaseShader.m 里处理这个时间间隔,并在方法 renderInRect:atTime: 被赋值给对应GLSL的uniform,也就是说, uTime 中存放了你app的流逝时间,以秒为单位。 

想要看到 uTime 起作用,就在 RWTNoise.fsh 的 main(void): 里加入以下代码: 

<code>float t = uTime/2.;
if (t>1.) {t -= floor(t);
}gl_FragColor = vec4(vec3(t), 1.);

这个简单算法会让你的屏幕重复从黑到白的淡入效果。

变量 t 是流逝时间的一半,需要被转换到颜色的取值范围 0.0 到 1.0 。函数 floor()返回最接近并小于或等于 t 的整数,将t减掉这个数就能符合要求。 

例如, uTime = 5.50: 则 t = 0.75 ,你的屏幕会75%的白。 

<code>t = 2.75
floor(t) = 2.00
t = t - floor(t) = 0.75

在你编译运行之前,记得在 RWTViewController.m 中把程序的片段着色器设成 RWTNoise : 

<code>self.shader = [[RWTBaseShader alloc] initWithVertexShader:@"RWTBase" fragmentShader:@"RWTNoise"];

现在可以编译运行来看你的简单动画!

你也可以通过把 if 语句改为以下这行代码来简化你的实现: 

<code>t = fract(t);

fract() 返回 t 的小数部分,这个值是通过 t – floor(t) 来计算的。这样看上去简单多了。现在你有了一个简单动画在工作,是时候来制造一点噪音(指的是Perline噪音)了。 

程序化纹理:“随机”噪声

fract() 是片段着色器编码中的一个基本函数,它保证所有的值都在 0.0 到 1.0 之间,你会用它来做一个 伪随机数生成器 (PRNG),以模拟 白噪声 图像。 

Perlin噪声模拟自然现象(例如木纹,大理石),而PRNG生成的值就刚好适用,因为它足够随机,可以看上去很自然,但是背后实际有数学函数来保证微妙的模式(例如相同的种子输入每次会生成相同的噪声输出)。

受控的混沌是程序化纹理图元的关键!

<code>注意:计算机随机性是一个引人入胜的主题,可以很容易展开成几十篇教程以及扩展的论坛讨论。Objective-C里的**[arc4random()](https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man3/arc4random.3.html)**对iOS开发者而言可是个奢侈品。你可以从[NSHipster](http://nshipster.com/random/),也就是Mattt Thompson这里了解更多。正如他优雅的总结:“一切通过随机性考验的,都只不过是一段隐藏的因果链”。

你要写的PRNG主要基于正弦波形,因为正弦波形的循环性对基于时间的输入刚好适用。而且正弦波形也很容易获得,直接调用 sin() 就好。 

它也很易于分析。大部分其他的GLSL PRNG要么就是 很伟大,但是无比复杂 ,要么就是 简单,但是不可靠 。 

首先,很快的直观回顾下 正弦波 : 

你可能已经很熟悉振幅 A 和波长 λ 了。不过如果没有的话也不用太担心,毕竟我们是要生成随机噪声,不是平滑波形。 

对于一个标准正弦波而言,从波峰到波谷,振幅取值范围是 -1.0 到 1.0 ,波长等于  (频率为1)。 

在上图中,你是从“前方”观察正弦波的,但是如果你从“上方”观察,将波峰设成白色,波谷设成黑色,就可以利用波峰和波谷来绘制平滑灰度渐变。

打开 RWTNoise.fsh ,将 main(void) 的内容替换为: 

<code>vec2 position = gl_FragCoord.xy/uResolution.xy;float pi = 3.14159265359;
float wave = sin(2.\*pi\*position.x);
wave = (wave+1.)/2.;gl_FragColor = vec4(vec3(wave), 1.);

记住 sin(2π) = 0, 对于当前像素而言,相当于你是将  与其沿着x轴的小数部分相乘。这样屏幕的最左端是正弦波的左边界,而最右端是正弦波的右边界。 

同时还要记住 sin 的输出是在-1到1之间的,所以需要将结果加1再除以2,这就将输出范围控制在了0到1之间。 

编译运行可以看到一个平滑的正弦波渐变,包含一个波峰和一个波谷:

将当前渐变转换成之前那样的图表则看起来像是这样:

现在,通过增加 频率 来把波长变短,并将屏幕y轴的坐标引入。 

把 wave 的计算改为: 

<code>float wave = sin(4.\*2.\*pi\*(position.x+position.y));

编译运行。你可以看到你的新波形不仅沿着屏幕对角线分布,而且还包含了更多的波峰和波谷(新的频率是4)。。

到目前为止,你着色器中的方程产生的是整洁、可预测的结果,并形成了有序的波纹。但我们的目标是无序而不是有序,所以现在要把局面打破一点。当然,我们是理智、可控的打破,不是砸向瓷器店那样的破坏。

将以下代码:

<code>float wave = sin(4.\*2.\*pi\*(position.x+position.y));
wave = (wave+1.)/2.;

替换为:

<code>float wave = fract(sin(16.\*2.\*pi\*(position.x+position.y)));

编译运行。你刚作的修改只是增加波形的频率,并用 fract() 函数来给渐变带来更陡的边缘。同时不再在不同的取值范围之间进行转换,这也给混乱程度加了一点儿料。 

着色器现在生成的纹样还是可预测的,所以我们再推上一把。

将 wave 的计算改为: 

<code>float wave = fract(10000.\*sin(16.\*(position.x+position.y)));

现在编译运行,可以看到胡椒和盐被洒落的效果。

乘数 10000 很适合产生伪随机数,而且通过以下 表格 ,可以很快的应用到正弦波上 

<code>Angle [sin](http://www.opengroup.org/onlinepubs/009695399/functions/sin.html)(a)
1.0   .0174
2.0   .0349
3.0   .0523
4.0   .0698
5.0   .0872
6.0   .1045
7.0   .1219
8.0   .1392
9.0   .1564
10.0  .1736

观察小数第二位的这一串数字:

<code>1, 3, 5, 6, 8, 0, 2, 3, 5, 7

再观察小数第四位的这一串数字:

<code>4, 9, 3, 8, 2, 5, 9, 2, 4, 6

跟第二串数比起来,第一串数的模式更为明显。虽然不一定永远正确,但低一些的小数位是我们挖掘伪随机数序列的一个很好起点。

另外很大的数还可能有非故意的 精度损失/溢出 错误,这对随机效果更有帮助。 

这时候你可能还能在屏幕上看出一点对角波纹的痕迹。如果看不出来,可能需要去找下你的验光师:)

这个淡淡的波纹是因为你给 position.x 和 position.y 同样的权重造成的。给每个轴增加一个不同的乘数就可以驱散这个痕迹,比如这样: 

<code>float wave = fract(10000.\*sin(128.\*position.x+1024.\*position.y));

是时候收拾一下了!在 main(void) 的上方增加如下函数 randomNoise(vec2 p) : 

<code>float randomNoise(vec2 p) {return fract(6791.*sin(47.*p.x+p.y*9973.));
}

这个PRNG的随机性主要取决于你对于乘数的选择。

以上的参数是我从 质数 表里选出来的,你也可以这样。如果你自行选择,我建议 p.x 用一个小一点的值,而 p.y 和 sin() 用大一点的值。 

接下来使用新的 randomNoise 函数重构你的着色器,将 main(void) 的内容用以下代码替换: 

<code>vec2 position = gl_FragCoord.xy/uResolution.xy;
float n = randomNoise(position);
gl_FragColor = vec4(vec3(n), 1.);

好啦!你现在就有个简单的基于sin函数的PRNG以用来生成2D噪声了。编译运行,然后值得歇一会来庆祝一下。

程序化纹理:方形网格

在跟3D球体打交道的时候, 归一化 的向量让方程变得简单了很多,对程序化纹理也一样,特别是噪声。类似平滑和插值的函数,如果是在矩形网格上作用就会简化很多。打开 RWTNoise.fsh 并将计算 position 的代码改为: 

<code>vec2 position = gl_FragCoord.xy/uResolution.xx;

这保证了 position 的单元尺寸与你的屏幕宽度( uResolution.x )一致。 

在下一行增加如下if语句:

<code>if ((position.x>1.) || (position.y>1.)) {discard;
}

热烈欢迎 discard 回到你的代码中,然后编译运行,可以看到渲染出如下图像: 

这个简单的方形就是你新的1×1像素着色器视口。

既然2D噪声可以沿着x和y无限扩展,那么如果你把噪声输入替换为以下任意一行:

<code>float n = randomNoise(position-1.);
float n = randomNoise(position+1.);

就能看到:

对于任何一个基于噪声的程序化纹理,太多噪声和噪声不够是完全不同的两个概念。幸运的是,将你的网格分块就能控制这种局面。

在你的 main(void) 增加如下代码,就在 n 前面: 

<code>float tiles = 2.;
position = floor(position*tiles);

然后编译运行!你可以看到如下的2*2方格:

乍看上去可能有点迷惑,解释如下:

floor(position*tiles) 会将任何值截取为小于或等于 position*tiles 的最接近整数。在两个方向上的取值都是在范围 (0.0, 0.0) 到 (2.0, 2.0) 中。 

如果没有 floor() ,这个范围会平滑连续,并且每个片段位置都会给 noise() 不同的种子。 

然而 floor() 生成了一个阶梯范围,就像在图中那样,在每个整数处停下。因此两个整数之间的每个 position 值都会被截成相同值,再作为 noise() 的种子,从而生成了整齐的网格图。 

网格瓦片个数选择的依据取决于你想要生成哪种类型的纹理效果。Perlin噪声引入了很多网格来模拟噪声模式,并且每一个的网格数都不同。

如果瓦片太多,就会生成斑驳的重复纹样。例如 tiles = 128 时,看起来就大概是这样: 

程序化纹理:平滑噪声

到此刻为止,你的噪声纹理,有一点,太噪声了。如果你只是想模拟一台没信号的老式学校电视机或者 MissingNo 还行。 

但是如果想要平滑一些的纹理怎么办呢?那么你可以使用 平滑 函数。准备好变速齿轮,开始图形处理课程101吧。 

在2D图像处理中,像素和它们的邻居有一定的 连通性 。一个八连像素有八个邻居像素围绕着自己;四个与边相邻,四个与顶点相连。 

这个概念也被称为 Moore近邻 ,如图所示,CC就是我们说的中心像素: 

<code>注意:想学习更多关于Moore近邻和图像处理的知识,请参看我们的[iOS教程系列之图像处理](http://www.raywenderlich.com/69855/image-processing-in-ios-part-1-raw-bitmap-modification)

一种常见的图像平滑操作是减弱图像的边缘频率,生成一份模糊/涂抹过的原图拷贝。这对你的矩形网格很有效,可以减少相邻瓦片之间的明显剧烈变化。

例如,如果白色瓦片被黑色瓦片包围,则平滑函数会调整瓦片颜色为浅灰。使用像下图这样的卷积 核 ),平滑函数会对每个像素生效: 

这是一个3×3的近邻平均滤波器,只是简单的通过对8个邻居求平均(使用相同权重)来进行平滑。要生成上述图像,需要这样的步骤:

<code>p = 0.1
p’ = (0.3+0.9+0.5+0.7+0.2+0.8+0.4+0.6+0.1) / 9
p’ = 4.5 / 9
p’ = 0.5

这不是最有意思的滤波器,但是最简单有效,易于实现!打开 RWTNoise.fsh ,并在 main(void) 上方增加如下函数: 

<code>float smoothNoise(vec2 p) {vec2 nn = vec2(p.x, p.y+1.);vec2 ne = vec2(p.x+1., p.y+1.);vec2 ee = vec2(p.x+1., p.y);vec2 se = vec2(p.x+1., p.y-1.);vec2 ss = vec2(p.x, p.y-1.);vec2 sw = vec2(p.x-1., p.y-1.);vec2 ww = vec2(p.x-1., p.y);vec2 nw = vec2(p.x-1., p.y+1.);vec2 cc = vec2(p.x, p.y);float sum = 0.;sum += randomNoise(nn);sum += randomNoise(ne);sum += randomNoise(ee);sum += randomNoise(se);sum += randomNoise(ss);sum += randomNoise(sw);sum += randomNoise(ww);sum += randomNoise(nw);sum += randomNoise(cc);sum /= 9.;return sum;
}

有点长,但是很直接。因为你的网格被分成了1×1的瓦片,因此不管在哪个方向 ±1. ,或者组合起来,都能让你得到一个相邻瓦片。片段是在GPU中被并行批处理的,所以在程序化纹理中,要知道相邻片段的值,只能实时计算。 

修改 main(void) 以拥有128个 瓦片 ,并通过 smoothNoise(position) 来计算 n 。经过这些修改,你的 main(void) 函数看起来应该像这样: 

<code>void main(void) {
  vec2 position = gl_FragCoord.xy/uResolution.xx;
  float tiles = 128.;
  position = floor(position*tiles);
  float n = smoothNoise(position);
  gl_FragColor = vec4(vec3(n), 1.);
}

编译运行!你被平滑函数击中了:P

每个像素都单独调用9次 randomNoise() 对于GPU而言是个沉重负担。你可以研究8连平滑函数,但是4连,也叫做 Von Neumann近邻 已经可以生成很好的平滑函数了。 

近邻平均的模糊效果有点儿粗糙,把你淳朴的噪声变成了灰色的泥浆。为了保留多一点原始的强度,执行如下卷积核:

这个新滤波器显著减少了近邻影响,原始中心像素在最后结果中占了50%,另外50%来自4个通过边相连的像素。对于上图而言,结果变成:

<code>p = 0.1
p’ = (((0.3+0.5+0.2+0.4) / 4) / 2) + (0.1 / 2)
p’ = 0.175 + 0.050
p’ = 0.225

快速挑战!看看你能不能在 smoothNoise(vec2 p) 中实现这个半邻平均滤波器。 

提示: 记住要去掉那些不用的相邻像素!你的GPU会因此感谢你,并回报以更快的渲染速度和更少的抱怨。 

平滑噪声滤波器的答案是:

<code>float smoothNoise(vec2 p) {vec2 nn = vec2(p.x, p.y+1.);vec2 ee = vec2(p.x+1., p.y);vec2 ss = vec2(p.x, p.y-1.);vec2 ww = vec2(p.x-1., p.y);vec2 cc = vec2(p.x, p.y);float sum = 0.;sum += randomNoise(nn)/8.;sum += randomNoise(ee)/8.;sum += randomNoise(ss)/8.;sum += randomNoise(ww)/8.;sum += randomNoise(cc)/2.;return sum;
}

如果你没做出来,看看答案,然后把自己的 smoothNoise 方法替换掉。将你的 tiles 减为8.,然后编译运行。 

你的纹理看起来更加自然,瓦片之间的过渡也更加平滑。比较上图(平滑噪声)和下图(随机噪声),感受一下平滑函数的效果。

到目前为止都干得不错:]

程序化纹理:插值噪声

噪声着色器的下一步是通过 双线性插值 ,即2D网格上的简单线性插值,来处理瓦片的明显边缘。 

为了便于理解,下图以你的噪声函数生成的2×2网格为例,标出了双线性插值需要的采样点:

通过对p点所处块的顶点取值并加以权重,瓦片就可以和相邻块混合到一起。因为每个瓦片都是1×1的单位瓦片, Q 点可以通过这样来取样噪声: 

<code>Q11 = smoothNoise(0.0, 0.0);
Q12 = smoothNoise(0.0, 1.0);
Q21 = smoothNoise(1.0, 0.0);
Q22 = smoothNoise(1.0, 1.0);

在代码里,你可以对 p 组合调用 floor() 和 ceil() 函数来实现,在 RWTNoise.fsh 的 main(void) 上增加如下函数: 

<code>float interpolatedNoise(vec2 p) {float q11 = smoothNoise(vec2(floor(p.x), floor(p.y)));float q12 = smoothNoise(vec2(floor(p.x), ceil(p.y)));float q21 = smoothNoise(vec2(ceil(p.x), floor(p.y)));float q22 = smoothNoise(vec2(ceil(p.x), ceil(p.y)));// 计算 R 的值// 返回 P 的值
}

GLSL已经包含了一个线性插值函数 mix() 。 

你将用它来计算 R1 和 R2 ,对于在y轴上处于相同高度的两个 Q 点,加入 fract(p.x) 作为权重。将以下代码加到 interpolatedNoise(vec2 p) 的最后: 

<code>float r1 = mix(q11, q21, fract(p.x));
float r2 = mix(q12, q22, fract(p.x));

最后,使用 mix() 对两个 R 值进行插值,并用 fract(p.y) 作为浮点权重。你的函数看上去应该是这样: 

<code>float interpolatedNoise(vec2 p) {float q11 = smoothNoise(vec2(floor(p.x), floor(p.y)));float q12 = smoothNoise(vec2(floor(p.x), ceil(p.y)));float q21 = smoothNoise(vec2(ceil(p.x), floor(p.y)));float q22 = smoothNoise(vec2(ceil(p.x), ceil(p.y)));float r1 = mix(q11, q21, fract(p.x));float r2 = mix(q12, q22, fract(p.x));return mix (r1, r2, fract(p.y));
}

因为你的新函数需要使用浮点权重来平滑,并在采样时用到了 floor() 和 ceil() ,你需要把 main(void) 里的 floor() 去掉。 

将以下代码:

<code>float tiles = 8.;
position = floor(position*tiles);
float n = smoothNoise(position);

替换为:

<code>float tiles = 8.;
position *= tiles;
float n = interpolatedNoise(position);

编译运行。明显的瓦片边缘消失了。。。

。。。但还是有明显的“星状”图案,这也是 意料之中 的事情。 

我们通过 smoothstep 函数来处理掉这个图案。 smoothstep() 是一个使用三次插值的函数,有着漂亮的曲线,比简单线性插值看上去好很多。 

“Smoothstep就像魔法盐一样,你可以把它洒在任何东西上面以让其看起来更好。”——Jari Komppa

在 interpolatedNoise(vec2 p) 的最前面加入以下代码: 

<code>vec2 s = smoothstep(0., 1., fract(p));

现在就能使用被smoothstep处理过的 s 作为 mix() 函数的权重了,像这样: 

<code>float r1 = mix(q11, q21, s.x);
float r2 = mix(q12, q22, s.x);return mix (r1, r2, s.y);

编译运行,星星就消失了!

星星是不见了,但是还是能看有一点像迷宫的图案。这是因为你的方形网格分成了8×8块,把 tiles 减少到 4. ,再编译运行! 

好多了。

你的噪声函数在网格边缘还是有点粗糙,不过已经可以作为浓烟或者模糊的影子的纹理元使用了。

程序化纹理:移动的噪声

最后一步!希望你还没忘记 uTime ,因为现在是时候让你的噪声动起来,只用在 main(void) 中,在对 n 赋值前加入一行: 

<code>position += uTime;

编译运行。

你的噪声纹理会朝着左下角移动,但是实际上是你的网格在朝着右上角(+x,+y的方向)移动。记住2D噪声是在所有方向无限扩展的,所以你的动画永远都是无缝的。

像素着色器绘制的月球

猜想:球体+噪声=月亮?你马上就能得到答案!

在这个教程的最后,你将把你的球体着色器和噪声着色器,在 RWTMoon.fsh 里合并成一个简单的 月球 着色器。你已经掌握了所有的信息,是时候接受这个伟大挑战了。 

提示: 你的噪声 瓦片数 现在要根据球体 半径 来决定,所以将以下代码: 

<code>float tiles = 4.;
position *= tiles;

替换为简单的:

<code>position /= radius;

同时我建议你通过这个函数来重构部分代码:

<code>float diffuseSphere(vec2 p, float r) {
}

狼人出没,请小心,答案是:

<code>//  RWTMoon.fsh
//
// Precision
precision highp float;// Uniforms
uniform vec2 uResolution;
uniform float uTime;// Constants
const vec3 cLight = normalize(vec3(.5, .5, 1.));float randomNoise(vec2 p) {return fract(6791.*sin(47.*p.x+p.y*9973.));
}float smoothNoise(vec2 p) {vec2 nn = vec2(p.x, p.y+1.);vec2 ee = vec2(p.x+1., p.y);vec2 ss = vec2(p.x, p.y-1.);vec2 ww = vec2(p.x-1., p.y);vec2 cc = vec2(p.x, p.y);float sum = 0.;sum += randomNoise(nn)/8.;sum += randomNoise(ee)/8.;sum += randomNoise(ss)/8.;sum += randomNoise(ww)/8.;sum += randomNoise(cc)/2.;return sum;
}float interpolatedNoise(vec2 p) {vec2 s = smoothstep(0., 1., fract(p));float q11 = smoothNoise(vec2(floor(p.x), floor(p.y)));float q12 = smoothNoise(vec2(floor(p.x), ceil(p.y)));float q21 = smoothNoise(vec2(ceil(p.x), floor(p.y)));float q22 = smoothNoise(vec2(ceil(p.x), ceil(p.y)));float r1 = mix(q11, q21, s.x);float r2 = mix(q12, q22, s.x);return mix (r1, r2, s.y);
}float diffuseSphere(vec2 p, float r) {float z = sqrt(r*r - p.x*p.x - p.y*p.y);vec3 normal = normalize(vec3(p.x, p.y, z));float diffuse = max(0., dot(normal, cLight));return diffuse;
}void main(void) {vec2 center = vec2(uResolution.x/2., uResolution.y/2.);float radius = uResolution.x/2.;vec2 position = gl_FragCoord.xy - center;if (length(position) > radius) {discard;}// Diffusefloat diffuse = diffuseSphere(position, radius);// Noiseposition /= radius;position += uTime;float noise = interpolatedNoise(position);gl_FragColor = vec4(vec3(diffuse*noise), 1.);
}

记住在 RWTViewController.m 里把代码的片段着色器改成 RWTMoon : 

<code>self.shader = [[RWTBaseShader alloc] initWithVertexShader:@"RWTBase" fragmentShader:@"RWTMoon"];

做完这一步之后,可以随意的把你的 glClearColor() 改成更适合这个场景的颜色(个人选择 xkcd的午夜紫 ): 

<code>glClearColor(.16f, 0.f, .22f, 1.f);

编译运行!我肯定Ozzy Osbourne会喜欢这个的。

何去何从?

你可以下载到 完整的项目 ,包括了本篇OpenGL ES像素着色器教程里所有的代码和资源。你也可以在 GitHub 找到这个代码仓库。 

祝贺,你已经对着色器和GPU颇有了解了。这是一篇很不一样并且很难的教程,真心为你的努力鼓掌。

你现在应该懂得如何利用GPU的巨大力量,加上聪明地利用数学就可以创造出有意思的逐像素渲染效果。你也应该理解了GLSL的函数、语法和结构。

本教程并没有包含太多Objective-C的内容,所以回到你的CPU,并看看能不能更酷的操控着色器吧!

试着增加统一的变量来获得触控点,陀螺仪数据,或者话筒输入。浏览器+WebGL可能更强大,但移动设备+OpenGL ES却更有趣:]

从这里开始可以引出多种探索的道路,这里有些建议:

  • 想让你的着色器性能出众?看看 Apple的OpenGL ES调适建议 (对iPhone 5s特别推荐) 
  • 想进一步了解Perlin噪声并完善你的实现?请查看Ken自己的 快速介绍 或者 详细历史 。 
  • 觉得还应该加强下基础?Toby有 你想要的 。 
  • 或者,只是或者,你觉得你已经做好进阶的准备了?那么去 Shadertoy 看看大师作品,如果你也提交了什么内容,给我们留言! 

总的来说,我建议你先直接去 GLSL沙盒 看看那不可思议的画廊。 

在那里你可以看到各种水平和用途的着色器,而且这个画廊是被WebGL和OpenGL ES界的一些大牛编辑/维护的。他们是真正闪耀的明星,促使了这篇教程的诞生,并绘制了3D图形学的未来,无比感谢他们!(特别是 @mrdoob , @iquilezles , @alteredq ) 


查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 马士兵 java 视频

    《尚学堂马士兵 Java 视频集锦(全)》最新版[压缩包]_ed2kers电驴资源下载网站_让分享继续 http://www.ed2kers.com/教育/计算机/309657.html...

    2024/4/27 19:34:49
  2. 原来多多输入法生成器可以换图标

    图标是用在产品上的标志,英文叫icon,自然后缀为.ico。 看了多多输入法生成器指导,一共三十四个图标,其中19个是16*16像素,12个是32*32像素,3个是48*48像素,从样子就可以看出分别是在哪里的。 如何自制图标文件 先用画图新建,调整画布大小,保存为png文件,因为画图不能保…...

    2024/4/27 16:08:57
  3. Tomcat配置数据源及部署项目

    一般我们部署项目到tomcat上都是将项目放到tomcat安装目录的webapps目录下,今天在这里讲一下tomcat配置数据源及部署项目的其他方法,仅供大家学习参考,有错误的地方欢迎大家指出,互相学习,谢谢! 配置数据源及项目部署 1、环境准备:在lib目录下导入数据库连接驱动包。这里…...

    2024/4/27 18:10:03
  4. 项目评审

    1.one team:可增设标记提醒功能(闹钟),可结合每日课表,每日作业,另外界面颜色需要改善,使其易于区分。2.FFF团:界面不美观,看着比较繁琐,需要改善,可增加提取微信支付记录功能。3.扬梦之舟:数据最好存在云端,使其可增设排行榜功能以更好地吸引人使用。4.盈风战队:…...

    2024/4/27 17:18:17
  5. SQL注入漏洞测试(POST)

    任意输入用户名密码 开启burpsuite,设置代理,截断登陆POST请求的数据包 将数据包保存为txt文件,打开sqlmap 开始爆数据库,表,列,字段值,例如:sqlmap -r txt文件路径 --dbs;sqlmap -r txt文件路径 -D 数据库名 --tables;sqlmap -r txt文件路径 -D 数据库名 -T 表名 --c…...

    2024/4/27 18:05:13
  6. java视频添加水印(logo)和视频的自动取帧

    在前一篇中详细讲解了图片添加水印,详见图片添加水印 在本章中,将为视频添加水印或logo,在给视频添加水印前我们先下载工具,在本章中使用的是FFPeng,这是一款常用的视频处理工具,下载链接点击打开链接 下载后无需安装,以下为源代码: CmdExecuter类package com.pqb.mark…...

    2024/4/27 13:28:43
  7. ASP+ACCESS SQL注入漏洞修复代码

    最近发现公司的网站被写入了些代码,应该是被注入入侵了,我们的企业网站是几年前做的,使用的是asp+access做的,现在在网上找到了一些修复漏洞的方法先给两种万能的防注入代码(放置conn.asp中) 第一种: squery=lcase(Request.ServerVariables("QUERY_STRING")) …...

    2024/4/27 13:42:43
  8. 人脸照片秒变艺术肖像画:清华大学提出APDrawingGAN CVPR 2019 oral paper

    该项工作被CVPR 2019录取为oral paper。CVPR是计算机视觉和人工智能领域内的国际顶级会议,2019共收到投稿5160篇,录取1300篇,其中oral paper288篇,仅占全部投稿的5.6%。作者制作了一个微信小程序展示APDrawingGAN的效果,小程序二维码如下,免费使用,快来试试吧: 肖像画是…...

    2024/4/27 13:58:13
  9. 52 WebGL不使用任何方法绘制矩形

    案例查看地址:点击这里由于上一个项目需要使用多个程序对象,而突然发现自己使用了好长时间的教程内置的方法,缺没有独立的写过相关的方法。故今天自己手动写了一个最原生的矩形。也发现了很多自己的不足,希望自己继续努力。案例代码:<!DOCTYPE html> <html lang=…...

    2024/4/27 18:26:06
  10. Tomcat安装及配置详解

    转载:http://www.ttlsa.com/tomcat/tomcat-install-and-configure/一,Tomcat简介Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,它早期的名称为catalina,后来由Apache、S…...

    2024/4/27 13:26:07
  11. 黑客“手艺人”的美丽与哀愁

    杨坤是一个有技术的男人。他和几位同样技术高超的青年男女愉快地生活在一起。他们的技术是:从看似平静如水的网站环境中找到漏洞的暗流。目前杨坤有三个身份:蓝莲花网络安全竞技战队队长清华大学计算机系在读博士安全公司长亭科技创始人之一这三个头衔听起来不甚性感,不过这…...

    2024/4/17 19:12:46
  12. 一代狙神是Johnny.R

    Johnny R.1985年出生,成为一个神话,不过谁也无法证明他是世界上最好的Cser,因为AWP相当局限性的表演无法完全说明问题,没有在重大LAN赛事获得成绩也是一个硬伤,年轻的他离那个颠峰还很远很远。ID:Johnny R.  真实姓名:Jonas Bollack  出生日期:1985.05.23  国籍:…...

    2024/4/19 9:53:29
  13. 仅训练996个剧本,迪士尼用AI自动生成动画

    源 / 智东西精选近日,迪士尼研究所和罗格斯大学的科学家共同发表了关于AI文本生成动画模型的论文。研究人员表示,这种算法只要在输入的文本中描述某些活动即可,不需要注释数据和进行大量训练就能产生动画。这篇论文中,研究人员进一步提出了端到端模型,这种模型可以创建一个…...

    2024/4/27 15:46:24
  14. 超详细Tomcat配置文件解读

    1、了解tomcat Tomcat不是一个完整意义上的Jave EE(j2ee)服务器,因为它没有提供完整的Java EE企业应用平台的API。但是由于Tomcat遵循apache开源协议,并且对当前Java开发框架开源组件Structs、Spring和Hibernate等实现完美支持,因此tomcat被众多企业用来部署配置众多的Jav…...

    2024/4/18 9:42:54
  15. Android解决数据库注入漏洞风险

    在app功能开发完成,提交应用市场时,竟然报高风险,有数据库注入漏洞!什么是数据库注入漏洞,又是怎么检测出来的,要怎样防止呢?SQL注入漏洞检测方式说明:主要就是检测,是否在query()中使用拼接字符串组成SQL语句的形式去查询数据库,此时容易发生SQL注入攻击。举一个例子…...

    2024/4/17 19:12:10
  16. Java断点续传下载视频

    控制层类: package com.grab.video.controller;import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodi…...

    2024/4/17 19:11:11
  17. python 20行代码爬取王者荣耀全英雄皮肤

    引言 王者荣耀大家都玩过吧,没玩过的也应该听说过,作为时下最火的手机MOBA游戏,咳咳,好像跑题了。我们今天的重点是爬取王者荣耀所有英雄的所有皮肤,而且仅仅使用20行Python代码即可完成。准备工作 爬取皮肤本身并不难,难点在于分析,我们首先得得到皮肤图片的url地址,话…...

    2024/4/19 1:11:11
  18. WebGL绘制3D文字(非中文)

    本教程是我在贴吧为吧友写的,搬到博客以备份。demo地址:cool.js——3D文字源码地址:cool.js此教程介绍一种简单粗暴的绘制3D文字的方案。本教程以数字为例。核心思想:制作或下载阿拉伯数字的二维几何面(Geometry)数据在程序中将二维面“拔”成三维(难点)渲染 第一步。我自…...

    2024/4/19 21:48:41
  19. Tomcat7.0/8.0 详细安装配置

    Tomcat 7.0 、Tomcat8.0 详细安装配置图解,以及UTF-8编码配置注意:安装配置tomcat7.0及以上,需要先安装JDK1.7及以上才能支持。1、先下载tomcat压缩包Tomcat 7 :http://tomcat.apache.org/download-70.cgiTomcat 8 : http://tomcat.apache.org/download-80.cgi 根据自己…...

    2024/4/17 19:12:22
  20. GAN生成对抗网络从入门到实践——入门级

    自2014年Ian Goodfellow提出生成对抗网络(GAN)的概念后,生成对抗网络变成为了学术界的一个火热的研究热点,Yann LeCun更是称之为”过去十年间机器学习领域最让人激动的点子”.生成对抗网络包括一个生成器(Generator,简称G)生成数据,一个鉴别器(Discriminator,简称D)来鉴别真实…...

    2024/4/18 4:41:34

最新文章

  1. 集成学习算法学习笔记

    一、集成学习的基本思想 三个臭皮匠顶一个诸葛亮 集成学习会考虑多个评估器的建模结果&#xff0c;汇总后得到一个综合的结果&#xff0c;以此来获取比单个模型更好的回归或分类表现。 很多独立的机器学习算法&#xff1a;决策树、神经网络、支持向量机 集成学习构建了一组基…...

    2024/4/27 19:51:56
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 微信小程序实现左滑删除

    效果 实现思路 使用的是官方提供的movable-area 嵌套movable-view 1、movable-area&#xff1a;注意点&#xff0c;需要设置其高度&#xff0c;否则会出现列表内容重叠的现象。 2、由于movable-view需要向右移动&#xff0c;左滑的时候给删除控件展示的空间&#xff0c;故 mov…...

    2024/4/22 23:38:58
  4. 54.螺旋矩阵

    题目描述 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。示例 1&#xff1a;输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 示例 2&#xff1a;输入&#xff1a;matrix …...

    2024/4/25 11:11:19
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/26 20:12:18
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/27 4:00:35
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/26 21:56:58
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/27 9:01:45
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/26 22:01:59
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/27 11:43:08
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57