http://www.tairan.com/archives/7509


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

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

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

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

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

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

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

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

准备开始

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

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

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

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

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

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

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

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

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

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

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

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

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

像素着色器101:渐变

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

注意:为了节省篇幅,将重点放在本教程的算法和方程上,**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的下方加上:

// Uniforms
uniform vec2 uResolution;

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

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

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

RWTGradient.fshmain(void)里增加如下几行:

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

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

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

改为:

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

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

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

float gradient = position.x;

改成:

float gradient = position.y;

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

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

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

对角渐变的答案是:

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

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

像素着色器几何学

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

几何学:2D圆盘

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

// Uniforms
uniform vec2 uResolution;

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

// 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

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

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

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

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

细圆环的解决方案是:

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条件:

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

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

在像素着色器的术语里,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的前面。

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

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

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

gl_FragColor = vec4(vec3(z), 1.);

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

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

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

将这行:

z /= radius;

替换为:

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。为了更好的将你球体的法线可视化,我们从nc作如下转换:

-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

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

gl_FragColor = vec4(vec3(z), 1.);

替换成:

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

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

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

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

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

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 ,这些颜色又能映射回特定的法线:

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.fshmain(void)里加上以下代码:

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

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

然后将以下代码:

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

替换成:

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

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

注意:如果你想了解更多关于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和TobySchachman提供的模块解释/示例)。

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

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

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

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

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

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

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

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

程序化纹理:时间

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

// Uniforms
uniform vec2 uResolution;
uniform float uTime;

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

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

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

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

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

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

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

t = 2.75
floor(t) = 2.00
t = t - floor(t) = 0.75

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

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

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

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

t = fract(t);

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

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

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

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

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

注意:计算机随机性是一个引人入胜的主题,可以很容易展开成几十篇教程以及扩展的论坛讨论。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.01.0,波长等于(频率为1)。

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

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

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的计算改为:

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

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

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

将以下代码:

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

替换为:

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

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

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

wave的计算改为:

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

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

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

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

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

1, 3, 5, 6, 8, 0, 2, 3, 5, 7

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

4, 9, 3, 8, 2, 5, 9, 2, 4, 6

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

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

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

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

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

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

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

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

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

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

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

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

程序化纹理:方形网格

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

vec2 position = gl_FragCoord.xy/uResolution.xx;

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

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

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

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

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

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

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

就能看到:

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

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

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就是我们说的中心像素:

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

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

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

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

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)上方增加如下函数:

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)函数看起来应该像这样:

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连,也叫做VonNeumann近邻已经可以生成很好的平滑函数了。

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

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

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会因此感谢你,并回报以更快的渲染速度和更少的抱怨。

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

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点可以通过这样来取样噪声:

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.fshmain(void)上增加如下函数:

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()

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

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

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

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()去掉。

将以下代码:

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

替换为:

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

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

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

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

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

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

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

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

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赋值前加入一行:

position += uTime;

编译运行。

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

像素着色器绘制的月球

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

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

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

float tiles = 4.;
position *= tiles;

替换为简单的:

position /= radius;

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

float diffuseSphere(vec2 p, float r) {
}

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

//  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

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

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

glClearColor(.16f, 0.f, .22f, 1.f);

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

何去何从?

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

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

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

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

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

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

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

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

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

如果你有任何问题、评论或者建议,欢迎加入下面的讨论!

《OpenGL ES像素着色器教程》若为泰然网原创(翻译),禁止用于一切商业行为,转载请注明出处并通知泰然网!


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

相关文章

  1. WebGL与three.js

    前面学习了一些webgl的基础知识,现在就用一下three.js写一个小例子,记录一下学习的过程。 效果图:1.去github下载three.js,然后将它加载到网页中<script src="js/three.js"></script>2接着就该写一下参数了,主要的四大组件一个不能少 2.1生成“场景…...

    2024/4/17 19:13:10
  2. 好嗨游戏:战火重燃!2019LPL夏季赛精彩看点全盘点! || 附夏季赛赛程表

    文章首发于:好嗨游戏 还没从MSI比赛中IG翻车TL中回过神来,2019LPL夏季赛又悄然开始了,虽然IG重演了2018年MSI时RNG的剧情,但是电竞就是电竞,有输有赢有惊喜有意外太正常不过,还是让我们向前看,来关注新的赛季,战火重燃,各个战队都已经整装待发,让我们一起来看看本次夏…...

    2024/5/6 2:57:37
  3. 【重温基础的SQL注入】图文详细解说,java后台用mybatis框架的SQL注入漏洞和效果展示,以及预防

    1. SQL注入1.1原因描述:用户名和密码的参数是直接引用,可用拼接的方式。1.2 拼接方式 or 1=1 (这个可以作为参考原型,变种很多,百度都有) 主要是拼接成这样的SQL:效果如此:即使没有拼接查询张三,依然能查到数据库的张三,因为 or 后面 1=1永恒成立。具体…...

    2024/4/17 19:13:16
  4. STM32 F103 F407 F429 F767 芯片资源对比

    ...

    2024/4/19 12:38:29
  5. 敏捷领导力4.0 | 从爆品设计到高效交付的全新升级&完整打法

    企业做敏捷转型,一直很贵。经常听到某某企业动辄几千万招标买方案,或者,某某企业全体高管飞到国外上大师课。这些都是真的,不是炒作。当然,能真正帮助企业实现敏捷转型,给企业创造价值,也真的值这个钱。可惜,屈指可数。为什么?因为80%的敏捷培训,讲授的是知识,出发点…...

    2024/4/17 19:12:35
  6. java如何检测项目中sql注入漏洞

    1、sql注入漏洞,一般是从接口处发生的,所以,可以从项目中所有有api的地方开始排查,首先,排查是否存在直接传入的参数是String类型的,如果没有,那么这个接口不会有sql注入风险。如果有,需要进一步查看该参数变量是否直接参与了sql字符串拼接,如果发现采用的是参数绑定的…...

    2024/4/17 19:13:23
  7. Qt项目实战3:二维码生成器

    qrtool项目简介 二维码(Qrcode)现在越来越常用,扫码支付、扫码添加好友、扫码乘坐公交车和地铁,我们的生活已经与二维码息息相关。这里我们使用qt软件+qrencode开源库来生成、显示、保存二维码图片,并且支持简单的二维码容错率修改和大小修改。 ui效果图如下:qrencode开源…...

    2024/4/17 19:13:16
  8. 20个不可思议的 WebGL 示例和演示

    WebGL 是一项在网页浏览器呈现3D画面的技术,有别于过去需要安装浏览器插件,通过 WebGL 的技术,只需要编写网页代码即可实现3D图像的展示。WebGL 可以为 Canvas 提供硬件3D加速渲染,这样 Web 开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了。在这篇文章…...

    2024/4/17 19:13:16
  9. Java 实现视频下载功能

    public static boolean httpDownload(String httpUrl, String saveFile) {// 1.下载网络文件int byteRead;URL url;try {url = new URL(httpUrl);} catch (MalformedURLException e1) {e1.printStackTrace();return false;}try {//2.获取链接URLConnection conn = url.openConn…...

    2024/4/17 19:12:46
  10. STM32F429——GPIO

    除非特别说明,否则本部分适用于整个 STM32F4xx 系列七 .通用I/O简介7.1 GPIO 简介每个通用 I/O 端口包括 4 个 32 位配置寄存器(GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR 、GPIOx_PUPDR),2个32位数据寄存器( GPIOx_IDR 和GPIOx_ODR)、1个32位置位/复位寄存器(GPIOx_B…...

    2024/4/13 1:54:22
  11. PHP中怎样避免SQL注入漏洞和XSS跨站脚本攻击漏洞

    漏洞危害: 黑客利用精心组织的SQL语句,通过Web表单注入的Web应用中,从而获取后台DB的访问与存取权限。获取相应的权限之后,可以对网页和数据库进行进一步的篡改、挂马和跳板攻击行为。防止SQL注入代码:主要是利用magic_quotes_gpc指令或它的搭挡addslashes()函数;如果要自…...

    2024/4/17 19:12:40
  12. Python图形界面(Tkinter)六:Entry输入框(含API整理)

    组件描述 Entry组件为输入框。 程序实现 代码(1) import tkinter as tkroot = tk.Tk()# 设置标签信息 label1 = tk.Label(root, text=战队名称:) label1.grid(row=0, column=0) label2 = tk.Label(root, text=选手名字:) label2.grid(row=1, column=0)# 创建输入框 entry1 …...

    2024/4/18 22:38:27
  13. windows+unity+webgl一键化打包

    rem 需要配置############################################# @rem unity的安装目录 set UNITYPATH="C:\Program Files\Unity\Editor\Unity.exe" @rem 项目路径 set PROJECT_PRJPATH=%UnityPrjPath% @rem rar文件名字 set Rar_File_Name=3DComputerMachine.rar @rem …...

    2024/4/17 19:14:11
  14. JavaHTTP下载视频

    控制层类: 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/19 12:55:30
  15. 二维码生成器文档

    转载:https://gerald.top/article/33/qrcanvashttps://github.com/gera2ld/qrcanvas/wiki纯纯的Javascript版,不依赖任何第三方包(不依赖jQuery喔~),就可以轻松地生成二维码了,支持各种自定义,可以通过自定义颜色生成各种漂亮的、有个性的二维码。本站已支持使用此功能获…...

    2024/5/1 7:19:17
  16. HAL-F429-ADC DMA 多通道

    static void MX_ADC2_Init(void) {ADC_ChannelConfTypeDef sConfig;/**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) */hadc2.Instance = ADC2; //配置ADC实例hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DI…...

    2024/4/17 19:14:35
  17. 像素图的去像素化(Depixeling Pixel Art)

    (原文链接,请点击这里) 作者: Johannes Kopf (微软研究院)、Dani Lischinski(希伯来大学) 摘要我们提出了一种新颖的算法,从像素图导出一张光滑、与分辨率无关的矢量图,可以任意放大而不会失真。我们的算法完全保留原始输入像素图的缩放特征信息,并且通过分段光滑的…...

    2024/4/19 23:24:43
  18. 注入漏洞之sql注入漏洞

    注入漏洞1 SQL注入所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行指定的SQL语句。具体来说,它是利用现有应用程序,将SQL语句注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入SQL语句得到一个存在安全…...

    2024/4/20 11:57:12
  19. 自己开发的在线视频下载工具,基于Java多线程

    比如这个在线视频:我们可以正常播放,但是找不到下载按钮。 打开Chrome开发者工具,在Network标签页里能看到很多网络传输请求:随便看一个请求的响应,发现类型为video,大小为500多k。因此,这个在线视频被拆分成了若干500多k的小片段,然后通过浏览器下载到本地进行播放。这…...

    2024/4/17 19:14:05
  20. WordPress更新版本失败429 Too Many Requests

    为什么会出现这种情况?这是因为WordPress官方的cdn屏蔽了部分中国ip导致的,如何解决?1.从各种渠道下载离线安装包2.functions.php代码指向国内镜像安装包代码来自ws234.com//WordPress 自定义升级包 add_filter(site_transient_update_core, function($value){foreach ($val…...

    2024/4/17 19:14:17

最新文章

  1. 英语单词学习

    house of worship:宗教场所 dote: 喜爱 coffin:棺材 coffeine:咖啡因 expedient:权宜的 buster:破坏者 procrastinate: 拖延 gourmet:美食家 expound:阐述 narcissist:自我陶醉 assassinate:暗杀 salvage: 挽救 savage: 凶猛的 ulcer: 溃疡 obituary:讣告 arbitrary:武断的 abu…...

    2024/5/9 5:56:40
  2. 梯度消失和梯度爆炸的一些处理方法

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

    2024/5/7 10:36:02
  3. app上架-您的应用存在最近任务列表隐藏风险活动的行为,不符合华为应用市场审核标准。

    上架提示 您的应用存在最近任务列表隐藏风险活动的行为&#xff0c;不符合华为应用市场审核标准。 修改建议&#xff1a;请参考测试结果进行修改。 请参考《审核指南》第2.19相关审核要求&#xff1a;https://developer.huawei.com/consumer/cn/doc/app/50104-02 造成原因 …...

    2024/5/8 2:37:20
  4. PostCss:详尽指南之安装和使用

    引言 在现代前端开发中&#xff0c;CSS预处理器如Sass、Less等已经成为提升开发效率、增强代码可维护性的重要工具。然而&#xff0c;随着Web技术的发展&#xff0c;CSS的功能也在不断扩展&#xff0c;一些新的CSS语法&#xff08;如变量、自定义属性、CSS Grid等&#xff09;以…...

    2024/5/7 14:57:33
  5. 【外汇早评】美通胀数据走低,美元调整

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

    2024/5/8 6:01:22
  6. 【原油贵金属周评】原油多头拥挤,价格调整

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

    2024/5/7 9:45:25
  7. 【外汇周评】靓丽非农不及疲软通胀影响

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

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

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

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

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

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

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

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

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

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

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

    2024/5/7 11:36:39
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

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

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

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

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

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

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

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

    2024/5/8 20:48:49
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

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

    2024/5/7 9:26:26
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

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

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

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

    2024/5/8 19:33:07
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

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

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

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

    2024/5/8 20:38:49
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

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

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

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

    2024/5/6 21:42:42
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

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

    2024/5/4 23:54:56
  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