diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
commit | 33058f2b292b3a581333bdfb21b8f671898c5060 (patch) | |
tree | 51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/api/webgl_api/tutorial | |
parent | 8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff) | |
download | translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2 translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip |
initial commit
Diffstat (limited to 'files/zh-cn/web/api/webgl_api/tutorial')
9 files changed, 1219 insertions, 0 deletions
diff --git a/files/zh-cn/web/api/webgl_api/tutorial/adding_2d_content_to_a_webgl_context/index.html b/files/zh-cn/web/api/webgl_api/tutorial/adding_2d_content_to_a_webgl_context/index.html new file mode 100644 index 0000000000..460d6debee --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/adding_2d_content_to_a_webgl_context/index.html @@ -0,0 +1,284 @@ +--- +title: 使用 WebGL 创建 2D 内容 +slug: Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context +tags: + - Tutorial + - WebGL + - 着色器 +translation_of: Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context +--- +<p>{{WebGLSidebar("Tutorial")}} {{PreviousNext("Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL", "Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL")}}</p> + +<p>一旦创建WebGL上下文创建成功,你就可以在这个上下文里渲染画图了。而对我们而言最简单的事,莫过于绘制一个没有纹理的2D图形了。那就让我们从画出一个正方形开始吧。</p> + +<h2 id="渲染场景">渲染场景</h2> + +<p>在开始前,我们首先需要明确最重要的一点,虽然我们的例子只是画一个二维物体,但我们仍然是在把它画在一个三维空间里。所以,我们依然需要创建着色器,通过它来渲染我们的简单场景并画出我们的物体。往下,我们将展示正方形是怎样一步步被画出来的。</p> + +<h3 id="着色器">着色器</h3> + +<p><strong>着色器是</strong>使用 <a href="http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf" title="http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf">OpenGL ES 着色语言</a>(<strong>GLSL</strong>)编写的程序,它携带着绘制形状的顶点信息以及构造绘制在屏幕上像素的所需数据,换句话说,它负责记录着像素点的位置和颜色。</p> + +<p>绘制WebGL时候有两种不同的着色器函数,<strong>顶点着色器和片段着色器。</strong>你需要通过用GLSL 编写这些着色器,并将代码文本传递给WebGL , 使之在GPU执行时编译。顺便一提,顶点着色器和片段着色器的集合我们通常称之为<strong>着色器程序。</strong></p> + +<p>下面我们通过在WebGL 环境绘制一个2D图像的例子快速介绍这两种着色器。</p> + +<h4 id="顶点着色器">顶点着色器</h4> + +<p>每次渲染一个形状时,顶点着色器会在形状中的每个顶点运行。 它的工作是将输入顶点从原始坐标系转换到WebGL使用的缩放空间(<strong>clipspace</strong>)坐标系,其中每个轴的坐标范围从-1.0到1.0,并且不考虑纵横比,实际尺寸或任何其他因素。</p> + +<p>顶点着色器需要对顶点坐标进行必要的转换,在每个顶点基础上进行其他调整或计算,然后通过将其保存在由GLSL提供的特殊变量(我们称为gl_Position)中来返回变换后的顶点</p> + +<p>顶点着色器根据需要, 也可以完成其他工作。例如,决定哪个包含 {{interwiki("wikipedia", "texel_(graphics)", "texel")}}面部纹理的坐标,可以应用于顶点;通过法线来确定应用到顶点的光照因子等。然后将这些信息存储在<a href="/zh-CN/docs/XUL_%E7%A8%8B%E5%BA%8F%E6%89%93%E5%8C%85">变量(varyings)</a>或<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Data#Attributes">属性(attributes)</a>属性中,以便与片段着色器共享</p> + +<p>以下的顶点着色器接收一个我们定义的属性(aVertexPosition)的顶点位置值。之后这个值与两个4x4的矩阵(uProjectionMatrix和uModelMatrix)相乘; 乘积赋值给gl_Position。有关投影和其他矩阵的更多信息,<a href="https://webglfundamentals.org/webgl/lessons/webgl-3d-perspective.html">在这里您可能可以找到有帮助的文章</a>.。</p> + +<pre class="brush: js notranslate">// Vertex shader program + + const vsSource = ` + attribute vec4 aVertexPosition; + + uniform mat4 uModelViewMatrix; + uniform mat4 uProjectionMatrix; + + void main() { + gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; + } + `;</pre> + +<p>这个例子中,我们没有计算任何光照效果,因为我们还没有应用到场景,它将后面的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Lighting_in_WebGL">WebGL光照</a>章节介绍。同样我们也还没应用任何纹理,这将在<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL">WebGL添加纹理</a>章节补充。</p> + +<h4 id="片段着色器">片段着色器</h4> + +<p><strong>片段着色器</strong>在顶点着色器处理完图形的顶点后,会被要绘制的每个图形的每个像素点调用一次。它的职责是确定像素的颜色,通过指定应用到像素的纹理元素(也就是图形纹理中的像素),获取纹理元素的颜色,然后将适当的光照应用于颜色。之后颜色存储在特殊变量gl_FragColor中,返回到WebGL层。该颜色将最终绘制到屏幕上图形对应像素的对应位置。</p> + +<pre class="brush: js notranslate">const fsSource = ` + void main() { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + } + `;</pre> + +<h3 id="初始化着色器">初始化着色器</h3> + +<p>现在我们已经定义了两个着色器,我们需要将它们传递给WebGL,编译并将它们连接在一起。下面的代码通过调用loadShader(),为着色器传递类型和来源,创建了两个着色器。然后创建一个附加着色器的程序,将它们连接在一起。如果编译或链接失败,代码将弹出alert。</p> + +<pre class="brush: js notranslate">// +// 初始化着色器程序,让WebGL知道如何绘制我们的数据 +function initShaderProgram(gl, vsSource, fsSource) { + const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); + const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); + + // 创建着色器程序 + + const shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.linkProgram(shaderProgram); + + // 创建失败, alert + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram)); + return null; + } + + return shaderProgram; +} + +// +// 创建指定类型的着色器,上传source源码并编译 +// +function loadShader(gl, type, source) { + const shader = gl.createShader(type); + + // Send the source to the shader object + + gl.shaderSource(shader, source); + + // Compile the shader program + + gl.compileShader(shader); + + // See if it compiled successfully + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); + return null; + } + + return shader; +} +</pre> + +<p>loadShader函数将WebGL上下文,着色器类型和<code>源码</code>作为参数输入,然后按如下步骤创建和编译着色器:</p> + +<p>1. 调用{{domxref("WebGLRenderingContext.createShader", "gl.createShader()")}}.创建一个新的着色器。</p> + +<p>2. 调用{{domxref("WebGLRenderingContext.shaderSource", "gl.shaderSource()")}}.将源代码发送到着色器。</p> + +<p>3. 一旦着色器获取到源代码,就使用{{domxref("WebGLRenderingContext.compileShader", "gl.compileShader()")}}.进行编译。</p> + +<p>4. 为了检查是否成功编译了着色器,将检查着色器参数gl.COMPILE_STATUS状态。通过调用{{domxref("WebGLRenderingContext.getShaderParameter", "gl.getShaderParameter()")}}获得它的值,并指定着色器和我们想要检查的参数的名字(gl.COMPILE_STATUS)。如果返回错误,则着色器无法编译,因此通过{{domxref("WebGLRenderingContext.getShaderInfoLog", "gl.getShaderInfoLog()")}}从编译器中获取日志信息并alert,然后删除着色器返回null,表明加载着色器失败。</p> + +<p>5. 如果着色器被加载并成功编译,则返回编译的着色器。</p> + +<p>我们可以像这样调用这段代码</p> + +<pre class="brush: js notranslate"> const shaderProgram = initShaderProgram(gl, vsSource, fsSource);</pre> + +<p>在创建着色器程序之后,我们需要查找WebGL返回分配的输入位置。在上述情况下,我们有一个属性和两个uniforms。属性从缓冲区接收值。顶点着色器的每次迭代都从分配给该属性的缓冲区接收下一个值。uniforms类似于JavaScript全局变量。它们在着色器的所有迭代中保持相同的值。由于属性和统一的位置是特定于单个着色器程序的,因此我们将它们存储在一起以使它们易于传递</p> + +<pre class="brush: js notranslate">const programInfo = { + program: shaderProgram, + attribLocations: { + vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'), + }, + uniformLocations: { + projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), + modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'), + }, + };</pre> + +<h2 id="创建对象">创建对象</h2> + +<p>在画正方形前,我们需要创建一个缓冲器来存储它的顶点。我们会用到名字为 initBuffers() 的函数。当我们了解到更多WebGL 的高级概念时,这段代码会将有更多参数,变得更加复杂,并且用来创建更多的三维物体。</p> + +<pre class="brush: js notranslate">function initBuffers(gl) { + const positionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + + var vertices = [ + 1.0, 1.0, 0.0, + -1.0, 1.0, 0.0, + 1.0, -1.0, 0.0, + -1.0, -1.0, 0.0 + ]; + + gl.bufferData(gl.ARRAY_BUFFER, + new Float32Array(vertices), + gl.STATIC_DRAW); + + return { + position: positionBuffer, + }; +} +</pre> + +<p>这段代码简单给出了绘画场景的本质。首先,它调用 gl 的成员函数 {{domxref("WebGLRenderingContext.createBuffer()", "createBuffer()")}} 得到了缓冲对象并存储在顶点缓冲器。然后调用 {{domxref("WebGLRenderingContext.bindBuffer()", "bindBuffer()")}} 函数绑定上下文。</p> + +<p><span style="line-height: 1.5;">当上一步完成,我们创建一个Javascript 数组去记录每一个正方体的每一个顶点。然后将其转化为 WebGL 浮点型类型的数组,并将其传到 gl 对象的 </span> {{domxref("WebGLRenderingContext.bufferData()", "bufferData()")}}<span style="line-height: 1.5;"> 方法来建立对象的顶点。</span></p> + +<p><strong style="">绘制场景</strong></p> + +<p>当着色器和物体都创建好后,我们可以开始渲染这个场景了。因为我们这个例子不会产生动画,所以 drawScene() 方法非常简单。它还使用了几个工具函数,稍后我们会介绍。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 你可能会得到这样一段错误报告:“ mat4 is not defined”,意思是说你缺少<code>glmatrix</code>库。该库的js文件<a href="https://mdn.github.io/webgl-examples/tutorial/gl-matrix.js">gl-matrix.js</a>可以从<a href="https://github.com/mdn/webgl-examples/issues/20">这里</a>获得.</p> +</div> + +<pre class="brush: js notranslate">function drawScene(gl, programInfo, buffers) { + gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque + gl.clearDepth(1.0); // Clear everything + gl.enable(gl.DEPTH_TEST); // Enable depth testing + gl.depthFunc(gl.LEQUAL); // Near things obscure far things + + // Clear the canvas before we start drawing on it. + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Create a perspective matrix, a special matrix that is + // used to simulate the distortion of perspective in a camera. + // Our field of view is 45 degrees, with a width/height + // ratio that matches the display size of the canvas + // and we only want to see objects between 0.1 units + // and 100 units away from the camera. + + const fieldOfView = 45 * Math.PI / 180; // in radians + const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; + const zNear = 0.1; + const zFar = 100.0; + const projectionMatrix = mat4.create(); + + // note: glmatrix.js always has the first argument + // as the destination to receive the result. + mat4.perspective(projectionMatrix, + fieldOfView, + aspect, + zNear, + zFar); + + // Set the drawing position to the "identity" point, which is + // the center of the scene. + const modelViewMatrix = mat4.create(); + + // Now move the drawing position a bit to where we want to + // start drawing the square. + + mat4.translate(modelViewMatrix, // destination matrix + modelViewMatrix, // matrix to translate + [-0.0, 0.0, -6.0]); // amount to translate + + // Tell WebGL how to pull out the positions from the position + // buffer into the vertexPosition attribute. + { + const numComponents = 3; // pull out 3 values per iteration + const type = gl.FLOAT; // the data in the buffer is 32bit floats + const normalize = false; // don't normalize + const stride = 0; // how many bytes to get from one set of values to the next + // 0 = use type and numComponents above + const offset = 0; // how many bytes inside the buffer to start from + gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); + gl.vertexAttribPointer( + programInfo.attribLocations.vertexPosition, + numComponents, + type, + normalize, + stride, + offset); + gl.enableVertexAttribArray( + programInfo.attribLocations.vertexPosition); + } + + // Tell WebGL to use our program when drawing + + gl.useProgram(programInfo.program); + + // Set the shader uniforms + + gl.uniformMatrix4fv( + programInfo.uniformLocations.projectionMatrix, + false, + projectionMatrix); + gl.uniformMatrix4fv( + programInfo.uniformLocations.modelViewMatrix, + false, + modelViewMatrix); + + { + const offset = 0; + const vertexCount = 4; + gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount); + } +} +</pre> + +<p>第一步,用背景色擦除画布,接着建立摄像机透视矩阵。设置45度的视图角度,并且设置一个适合实际图像的宽高比。 指定在摄像机距离0.1到100单位长度的范围内的物体可见。</p> + +<p>接着加载特定位置,并把正方形放在距离摄像机6个单位的的位置。然后,我们绑定正方形的顶点缓冲到上下文,并配置好,再通过调用 {{domxref("WebGLRenderingContext.drawArrays()", "drawArrays()")}} 方法来画出对象。 </p> + +<p>{{EmbedGHLiveSample('webgl-examples/tutorial/sample2/index.html', 670, 510) }}</p> + +<p>如果你的浏览器支持WebGL的话,<a href="/samples/webgl/sample2">可以点击这里看看DEMO</a>。完整的源代码从<a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample2">这里</a>获得</p> + +<h2 id="矩阵通用计算">矩阵通用计算</h2> + +<p>矩阵计算是一个很复杂的运算。 没人会想去自己写完所有代码来处理这些运算。通常人们使用一个矩阵运算库,而不会自己实现矩阵运算。在这个例子中我们使用的是<a href="http://glmatrix.net/">glMatrix library</a>.</p> + +<h2 id="相关资料">相关资料</h2> + +<ul> + <li><a class="external" href="http://mathworld.wolfram.com/Matrix.html" title="http://mathworld.wolfram.com/Matrix.html">Matrices</a> 线上数学百科全书</li> + <li><a class="external" href="http://en.wikipedia.org/wiki/Matrix_(mathematics)" title="http://en.wikipedia.org/wiki/Matrix_(mathematics)">Matrix</a> 维基百科</li> +</ul> + +<p>{{PreviousNext("Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL", "Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL")}}</p> diff --git a/files/zh-cn/web/api/webgl_api/tutorial/animating_objects_with_webgl/index.html b/files/zh-cn/web/api/webgl_api/tutorial/animating_objects_with_webgl/index.html new file mode 100644 index 0000000000..6cfeee1284 --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/animating_objects_with_webgl/index.html @@ -0,0 +1,55 @@ +--- +title: 用WebGL让目标动起来 +slug: Web/API/WebGL_API/Tutorial/Animating_objects_with_WebGL +tags: + - WebGL + - 教程 +translation_of: Web/API/WebGL_API/Tutorial/Animating_objects_with_WebGL +--- +<p>{{WebGLSidebar("Tutorial")}} {{PreviousNext("Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL", "Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL") }}</p> + +<p>在此示例中,我们实际上将旋转正方形平面。</p> + +<h2 id="使正方形旋转"><font><font>使正方形旋转</font></font></h2> + +<p><font>让我们开始旋转正方形。</font><font>我们需要的第一件事是创建一个变量,用于跟踪正方形的当前旋转:</font></p> + +<pre class="brush: js">var squareRotation = 0.0; +</pre> + +<p><font><font>现在我们需要更新</font></font><code>drawScene()</code><font><font>函数以在绘制正方形时将当前旋转应用于正方形。</font><font>转换为正方形的初始绘图位置后,我们像这样应用旋转:</font></font></p> + +<pre class="brush: js"> mat4.rotate(modelViewMatrix, // destination matrix + modelViewMatrix, // matrix to rotate + squareRotation, // amount to rotate in radians + [0, 0, 1]); // axis to rotate around</pre> + +<p><font><font>这会将modelViewMatrix的当前值</font></font><code>squareRotation</code><font><font>绕Z轴旋转。</font></font></p> + +<p><font><font>要进行动画制作,我们需要添加</font></font><code>squareRotation</code><font><font>随时间</font><font>更改值的代码</font><font>。</font><font>为此,我们可以创建一个新变量来跟踪上次动画播放的时间(我们称之为</font></font><code>then</code><font><font>),然后将以下代码添加到主函数的末尾</font></font></p> + +<pre class="brush: js"> var then = 0; + + // Draw the scene repeatedly + function render(now) { + now *= 0.001; // convert to seconds + const deltaTime = now - then; + then = now; + + drawScene(gl, programInfo, buffers, deltaTime); + + requestAnimationFrame(render); + } + requestAnimationFrame(render);</pre> + +<p><font><font>该代码用于 </font></font><code>requestAnimationFrame</code><font><font> 要求浏览器</font></font><font><font>在每一帧上</font><font>调用函数“</font></font><code>render</code><font><font>”。</font></font><code>requestAnimationFrame</code><font><font> 自页面加载以来经过的时间(以毫秒为单位)。</font><font>我们将其转换为秒,然后从中减去,以计算</font></font><code>deltaTime</code><font><font> 自渲染最后一帧以来的</font><font>秒数 </font><font>。</font><font>在drawscene的结尾,我们添加了要更新的代码 </font></font><code>squareRotation.</code></p> + +<pre class="brush: js"><code> squareRotation += deltaTime;</code></pre> + +<p><font><font>该代码使用自上次我们更新值以来所经过的时间</font></font><code>squareRotation</code><font><font>来确定旋转正方形的距离。</font></font></p> + +<p>{{EmbedGHLiveSample('webgl-examples/tutorial/sample4/index.html', 670, 510) }}</p> + +<p><a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample4">查看完整代码</a> | <a href="http://mdn.github.io/webgl-examples/tutorial/sample4/">在新页面中打开示例</a></p> + +<p>{{PreviousNext("Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL", "Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL") }}</p> diff --git a/files/zh-cn/web/api/webgl_api/tutorial/animating_textures_in_webgl/index.html b/files/zh-cn/web/api/webgl_api/tutorial/animating_textures_in_webgl/index.html new file mode 100644 index 0000000000..763e9ae60d --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/animating_textures_in_webgl/index.html @@ -0,0 +1,144 @@ +--- +title: 动画纹理 +slug: Web/API/WebGL_API/Tutorial/Animating_textures_in_WebGL +tags: + - WebGL +translation_of: Web/API/WebGL_API/Tutorial/Animating_textures_in_WebGL +--- +<p>{{WebGLSidebar("Tutorial") }} {{Previous("Web/API/WebGL_API/Tutorial/Lighting_in_WebGL")}}</p> + +<p><font><font>在本演示中,我们以上一个示例为基础,将静态纹理替换为正在播放的mp4视频文件的帧。</font><font>实际上,这很容易做到,而且观看起来很有趣,所以让我们开始吧。</font><font>您可以使用类似的代码来使用任何类型的数据(例如</font></font>{{ HTMLElement("canvas") }}) <font><font>作为纹理的源。</font></font></p> + +<h2 id="获取视频">获取视频</h2> + +<p><font><font>第一步是创建</font></font>{{ HTMLElement("video") }}<font><font>将用于检索视频帧</font><font>的</font><font>元素:</font></font></p> + +<pre class="brush: js">// will set to true when video can be copied to texture +var copyVideo = false; + +function setupVideo(url) { + const video = document.createElement('video'); + + var playing = false; + var timeupdate = false; + + video.autoplay = true; + video.muted = true; + video.loop = true; + + // Waiting for these 2 events ensures + // there is data in the video + + video.addEventListener('playing', function() { + playing = true; + checkReady(); + }, true); + + video.addEventListener('timeupdate', function() { + timeupdate = true; + checkReady(); + }, true); + + video.src = url; + video.play(); + + function checkReady() { + if (playing && timeupdate) { + copyVideo = true; + } + } + + return video; +} +</pre> + +<p><font><font>首先,我们创建一个视频元素。</font><font>我们将其设置为自动播放,静音和循环播放视频。</font><font>然后,我们设置了两个事件以确保视频正在播放并且时间轴已更新。</font><font>我们需要进行这两项检查,因为如果将视频上传到WebGL尚无可用数据,它将产生错误。</font><font>检查这两个事件可确保有可用数据,并且可以安全地开始将视频上传到WebGL纹理。</font><font>在上面的代码中,我们确认是否同时发生了这两个事件。</font><font>如果是这样,我们将全局变量设置 </font></font><code>copyVideo</code><font><font>为true,以表示可以安全地开始将视频复制到纹理。</font></font></p> + +<p><font><font>最后,我们将</font></font><code>src</code><font><font>属性</font><font>设置</font><font>为start并调用</font></font><code>play</code><font><font> 以开始加载和播放视频。</font></font></p> + +<h2 id="用视频帧作为纹理">用视频帧作为纹理 </h2> + +<p><font><font>接下来的更改是</font></font><code>initTexture()</code><font><font>,它变得更加简单,因为它不再需要加载图像文件。</font><font>相反,它所做的只是创建一个空的纹理对象,在其中放置一个像素,然后设置其过滤条件供以后使用:</font></font></p> + +<pre class="brush: js">function initTexture(gl) { + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Because video has to be download over the internet + // they might take a moment until it's ready so + // put a single pixel in the texture so we can + // use it immediately. + const level = 0; + const internalFormat = gl.RGBA; + const width = 1; + const height = 1; + const border = 0; + const srcFormat = gl.RGBA; + const srcType = gl.UNSIGNED_BYTE; + const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + width, height, border, srcFormat, srcType, + pixel); + + // Turn off mips and set wrapping to clamp to edge so it + // will work regardless of the dimensions of the video. + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + return texture; +}</pre> + +<div><font><font>这里是</font></font><code>updateTexture()</code><font><font> 方法; </font><font>这是完成实际工作的地方:</font></font></div> + +<pre class="brush: js">function updateTexture(gl, texture, video) { + const level = 0; + const internalFormat = gl.RGBA; + const srcFormat = gl.RGBA; + const srcType = gl.UNSIGNED_BYTE; + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + srcFormat, srcType, video); +}</pre> + +<p><font><font>您之前已经看过此代码。</font><font>它与上一个示例中的image onload函数几乎相同-除非我们调用</font></font><code>texImage2D()</code><font><font>,而不是传递</font></font><code>Image</code><font><font>对象,而是传递</font></font>{{ HTMLElement("video") }}<font><font>元素。</font><font>WebGL知道如何拉出当前帧并将其用作纹理。</font></font></p> + +<p><font><font>然后,在</font></font><code>main()</code><font><font> 代替通话,以</font></font><code>loadTexture()</code><font><font>在前面的例子中,我们调用 </font></font><code>initTexture</code><code>()</code><font><font>之后</font></font><code>setupVideo()</code><font><font>。</font></font></p> + +<p><font><font>在</font></font><code>render()</code><font><font>if是否</font></font><code>copyVideo</code><font><font>为真的</font><font>定义中</font><font>,则 </font></font><code>updateTexture()</code><font><font>每次调用</font></font><code>drawScene()</code><font><font> 函数</font><font>之前都会调用一次 </font><font>。</font></font></p> + +<pre class="brush: js"> const texture = initTexture(gl); + + const video = setupVideo('Firefox.mp4'); + + var then = 0; + + // Draw the scene repeatedly + function render(now) { + now *= 0.001; // convert to seconds + const deltaTime = now - then; + then = now; + + if (copyVideo) { + updateTexture(gl, texture, video); + } + + drawScene(gl, programInfo, buffers, texture, deltaTime); + + requestAnimationFrame(render); + } + requestAnimationFrame(render);</pre> + +<p>下面是结果:</p> + +<p>{{EmbedGHLiveSample('webgl-examples/tutorial/sample8/index.html', 670, 510) }}</p> + +<p><a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample8">查看完整的代码</a> | <a href="http://mdn.github.io/webgl-examples/tutorial/sample8/">在新页中打开这个demo</a></p> + +<h2 id="参考">参考</h2> + +<ul> + <li><a href="/en/Using_HTML5_audio_and_video" title="En/Using audio and video in Firefox">Using audio and video in Firefox</a></li> +</ul> + +<p>{{Previous("Web/API/WebGL_API/Tutorial/Lighting_in_WebGL")}}</p> diff --git a/files/zh-cn/web/api/webgl_api/tutorial/creating_3d_objects_using_webgl/index.html b/files/zh-cn/web/api/webgl_api/tutorial/creating_3d_objects_using_webgl/index.html new file mode 100644 index 0000000000..88e1bf75e7 --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/creating_3d_objects_using_webgl/index.html @@ -0,0 +1,128 @@ +--- +title: Creating 3D objects using WebGL +slug: Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL +translation_of: Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL +--- +<p>{{WebGLSidebar("Tutorial")}} {{PreviousNext("Web/API/WebGL_API/Tutorial/Animating_objects_with_WebGL", "Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL")}}</p> + +<p>现在让我们给之前的正方形添加五个面从而可以创建一个三维的立方体。最简单的方式就是通过调用方法 {{domxref("WebGLRenderingContext.drawElements()", "gl.drawElements()")}} 使用顶点数组列表来替换之前的通过方法{{domxref("WebGLRenderingContext.drawArrays()", "gl.drawArrays()")}} 直接使用顶点数组。而顶点数组列表里保存着将会被引用到一个个独立的顶点。</p> + +<p>其实现在会存在这样一个问题:每个面需要4个顶点,而每个顶点会被3个面共享。我们会创建一个包含24个顶点的数组列表,通过使用数组下标来索引顶点,然后把这些用于索引的下标传递给渲染程序而不是直接把整个顶点数据传递过去,这样来减少数据传递。那么也许你就会问:那么使用8个顶点就好了,为什么要使用24个顶点呢?这是因为每个顶点虽然被3个面共享但是它在每个面上需要使用不同的颜色信息。24个顶点中的每一个都会有独立的颜色信息,这就会造成每个顶点位置都会有3份副本。</p> + +<h2 id="定义立方体顶点位置">定义立方体顶点位置</h2> + +<p>首先,更新 <code>initBuffers() 函数代码</code>创建顶点位置数据缓存。现在的代码看起来和渲染正方形时的代码很相似,只是比之前的代码更长因为现在有了24个顶点(每个面使用4个顶点):</p> + +<pre class="brush: js">var vertices = [ + // Front face + -1.0, -1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, 1.0, 1.0, + -1.0, 1.0, 1.0, + + // Back face + -1.0, -1.0, -1.0, + -1.0, 1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, -1.0, -1.0, + + // Top face + -1.0, 1.0, -1.0, + -1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, + + // Bottom face + -1.0, -1.0, -1.0, + 1.0, -1.0, -1.0, + 1.0, -1.0, 1.0, + -1.0, -1.0, 1.0, + + // Right face + 1.0, -1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, + + // Left face + -1.0, -1.0, -1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, 1.0, -1.0 +]; +</pre> + +<h2 id="定义顶点颜色">定义顶点颜色</h2> + +<p>然后我们还要为每个顶点定义颜色。下面的代码首先为每个面定义颜色,然后用一个循环语句为每个顶点定义颜色信息。</p> + +<pre class="brush: js">var colors = [ + [1.0, 1.0, 1.0, 1.0], // Front face: white + [1.0, 0.0, 0.0, 1.0], // Back face: red + [0.0, 1.0, 0.0, 1.0], // Top face: green + [0.0, 0.0, 1.0, 1.0], // Bottom face: blue + [1.0, 1.0, 0.0, 1.0], // Right face: yellow + [1.0, 0.0, 1.0, 1.0] // Left face: purple +]; + +var generatedColors = []; + +for (j=0; j<6; j++) { + var c = colors[j]; + + for (var i=0; i<4; i++) { + generatedColors = generatedColors.concat(c); + } +} + +var cubeVerticesColorBuffer = gl.createBuffer(); +gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesColorBuffer); +gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(generatedColors), gl.STATIC_DRAW); +</pre> + +<h2 id="定义元素(三角形)数组">定义元素(三角形)数组</h2> + +<p>既然已经创建好了顶点数组,接下来就要创建元素(三角形)数组了。</p> + +<pre class="brush: js">var cubeVerticesIndexBuffer = gl.createBuffer(); +gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer); + +// This array defines each face as two triangles, using the +// indices into the vertex array to specify each triangle's +// position. + +var cubeVertexIndices = [ + 0, 1, 2, 0, 2, 3, // front + 4, 5, 6, 4, 6, 7, // back + 8, 9, 10, 8, 10, 11, // top + 12, 13, 14, 12, 14, 15, // bottom + 16, 17, 18, 16, 18, 19, // right + 20, 21, 22, 20, 22, 23 // left +]; + +// Now send the element array to GL + +gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, + new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW); +</pre> + +<p><code><font face="Open Sans, Arial, sans-serif">代码中的 </font>cubeVertexIndices</code> 数组声明每一个面都使用两个三角形来渲染。通过立方体顶点数组的索引指定每个三角形的顶点。那么这个立方体就是由12个三角形组成的了。</p> + +<h2 id="渲染立方体">渲染立方体</h2> + +<p>接下来就需要在 <code>drawScene()</code> 函数里添加代码使用立方体顶点索引数据来渲染这个立方体了。代码里添加了对 {{domxref("WebGLRenderingContext.bindBuffer()", "gl.bindBuffer()")}} 和 {{domxref("WebGLRenderingContext.drawElements()", "gl.drawElements()")}}的调用:</p> + +<pre class="brush: js">gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer); +setMatrixUniforms(); +gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0); +</pre> + +<p>立方体的每个面都由2个三角形组成,那就是每个面需要6个顶点,或者说总共36个顶点,尽管有许多重复的。然而,因为索引数组的每个元素都是简单的整数类型,所以每一帧动画需要传递给渲染程序的数据也不是很多。</p> + +<p>到现在为止,我们已经创建了一个颜色生动的并且会在场景中移动和旋转的立方体,这一定很酷吧。</p> + +<p>{{EmbedGHLiveSample('webgl-examples/tutorial/sample5/index.html', 670, 510) }}</p> + +<p><a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample5">查看全部源代码</a> | <a href="http://mdn.github.io/webgl-examples/tutorial/sample5/">在新页面打开示例</a></p> + +<p>{{PreviousNext("Web/API/WebGL_API/Tutorial/Animating_objects_with_WebGL", "Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL")}}</p> diff --git a/files/zh-cn/web/api/webgl_api/tutorial/getting_started_with_webgl/index.html b/files/zh-cn/web/api/webgl_api/tutorial/getting_started_with_webgl/index.html new file mode 100644 index 0000000000..efbdc9fd7f --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/getting_started_with_webgl/index.html @@ -0,0 +1,70 @@ +--- +title: 初识 WebGL +slug: Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL +tags: + - WebGL + - 教程 +translation_of: Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL +--- +<p>{{WebGLSidebar("Tutorial")}} {{Next("Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context")}}</p> + +<p><a class="external" href="http://www.khronos.org/webgl/" title="http://www.khronos.org/webgl/">WebGL</a> 使得在支持HTML 的 <a class="internal" href="/zh-CN/HTML/Canvas" title="zh-CN/HTML/Canvas"><code>canvas</code></a> 标签的浏览器中,不需要安装任何插件,便可以使用基于 <a class="external" href="http://www.khronos.org/opengles/" title="http://www.khronos.org/opengles/">OpenGL ES</a> 2.0 的 API 在 canvas 中进行2D和3D渲染。WebGL程序包括用 JavaScript 写的控制代码,以及在图形处理单元(GPU, Graphics Processing Unit)中执行的着色代码(GLSL,注:GLSL为OpenGL着色语言)。WebGL 元素可以和其他 HTML 元素混合使用,并且可以和网页其他部分或者网页背景结合起来。</p> + +<p>本文将向您介绍 WebGL 的基本用法。此处假定您对三维图形方面的数学知识已经有一定的理解,本文也不会试图向您教授 3D图像概念本身。</p> + +<p>本文的代码也可以在这里下载 <a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial">webgl-examples GitHub repository</a> 。</p> + +<p><a href="https://threejs.org/">THREE.js</a>和<a href="https://www.babylonjs.com/">BABYLON.js</a>等很多框架封装了WebGL,提供了各个平台之间的兼容性。使用这些框架而非原生的WebGL可以更容易地开发3D应用和游戏。</p> + +<h2 id="准备_3D_渲染">准备 3D 渲染</h2> + +<p>为了使用 WebGL 进行 3D 渲染,你首先需要一个 canvas 元素。下面的 HTML 片段用来建立一个 canvas 元素并设置一个 onload 事件处理程序来初始化我们的 WebGL 上下文 。</p> + +<pre class="brush: html notranslate"><body onload="main()"> + <canvas id="glcanvas" width="640" height="480"> + 你的浏览器似乎不支持或者禁用了HTML5 <code>&lt;canvas&gt;</code> 元素. + </canvas> +</body> +</pre> + +<h3 id="准备_WebGL_上下文">准备 WebGL 上下文</h3> + +<p>我们的 JavaScript 代码中的 <code>main()</code> 函数将会在文档加载完成之后被调用。它的任务是设置 WebGL 上下文并开始渲染内容。<code class="language-js"> </code></p> + +<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">// 从这里开始 +function main() { + const canvas = document.querySelector("#glcanvas"); + // 初始化WebGL上下文 + const gl = canvas.getContext("webgl"); + + // 确认WebGL支持性 + if (!gl) { + alert("无法初始化WebGL,你的浏览器、操作系统或硬件等可能不支持WebGL。"); + return; + } + + // 使用完全不透明的黑色清除所有图像 + gl.clearColor(0.0, 0.0, 0.0, 1.0); + // 用上面指定的颜色清除缓冲区 + gl.clear(gl.COLOR_BUFFER_BIT); +}</code></pre> + +<p>我们所要做的第一件事就是是获取 canvas 的引用,把它保存在 ‘canvas’ 变量里。</p> + +<p>当我们获取到 canvas之后,我们会调用<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext">getContext</a> 函数并向它传递<code>"webgl"</code>参数,来尝试获取<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext">WebGLRenderingContext</a>。如果浏览器不支持webgl,<code>getContext</code>将会返回<code>null</code>,我们就可以显示一条消息给用户然后退出。</p> + +<p>如果WebGL上下文成功初始化,变量 ‘gl’ 会用来引用该上下文。在这个例子里,我们用黑色清除上下文内已有的元素。(用背景颜色重绘canvas)。</p> + +<p>{{EmbedGHLiveSample('webgl-examples/tutorial/sample1/index.html', 670, 510) }}</p> + +<p><a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample1">看源码</a> | <a href="http://mdn.github.io/webgl-examples/tutorial/sample1/">看DEMO</a></p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="https://dev.opera.com/articles/introduction-to-webgl-part-1/">WebGL介绍</a>: 由Luz Caballero所著, 发布在dev.opera.com。 这篇文章说明WebGL是什么, 解释了WebGL是如何工作的 (介绍了渲染管线的概念),并且介绍了一些WebGL库。</li> + <li><a href="http://webglfundamentals.org/">WebGL基础</a></li> + <li><a href="http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Table-of-Contents.html">现代OpenGL介绍:</a> 由Joe Groff写的一系列关于OpenGL的不错的文章,提供了一个清晰的介绍,从OpenGL的历史到图形管线概念,也包括一些说明OpenGL如何工作的例子,如果你对OpenGL没有任何概念的话,这是不错的出发点。</li> +</ul> + +<p>{{Next("Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context")}}</p> diff --git a/files/zh-cn/web/api/webgl_api/tutorial/index.html b/files/zh-cn/web/api/webgl_api/tutorial/index.html new file mode 100644 index 0000000000..b73235a3d6 --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/index.html @@ -0,0 +1,41 @@ +--- +title: WebGL 教程 +slug: Web/API/WebGL_API/Tutorial +tags: + - WebGL + - 教程 + - 概览 +translation_of: Web/API/WebGL_API/Tutorial +--- +<div>{{WebGLSidebar}}</div> + +<div class="summary"> +<p><a class="external" href="http://www.khronos.org/webgl/" title="http://www.khronos.org/webgl/">WebGL</a> 使得网页在支持HTML {{HTMLElement("canvas")}} 标签的浏览器中,不需要使用任何插件,便可以使用基于 <a href="http://www.khronos.org/opengles/" title="http://www.khronos.org/opengles/">OpenGL ES</a> 2.0 的 API 在 canvas 中进行3D渲染. WebGL 程序由javascript的控制代码,和在计算机的图形处理单元(GPU, Graphics Processing Unit)中执行的特效代码(shader code,渲染代码) 组成. WebGL 元素可以和其他HTML元素混合, 并且会和页面的其他部分或页面背景相合成.</p> +</div> + +<p><span class="seoSummary">此教程从基础开始讲解如何使用<code><canvas></code> 元素来画WebGL 图形. 提供的例子会让你对WebGL有更清晰的认识, 并且会提供代码片段方便你构建自己的内容.</span></p> + +<h2 id="开始之前">开始之前</h2> + +<p>使用 <code><canvas></code> 元素并不困难,但你需要基本的 <a href="/en-US/docs/Web/HTML" title="HTML">HTML</a> 和 <a href="/en-US/docs/Web/JavaScript" title="JavaScript">JavaScript</a> 知识。一些老浏览器不支持<code><canvas></code> 元素和 WebGL,但所有最近的主流浏览器都支持它们.。为了能在canvas中绘制图形,我们使用Javascript的上下文环境(context)对象,此对象可以动态创建图形。</p> + +<h2 id="在本教程中">在本教程中</h2> + +<dl> + <dt><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL">开始WebGL</a></dt> + <dd>如何设置WebGL上下文环境.</dd> + <dt><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context">给WebGL的上下文环境添加2D内容</a></dt> + <dd>如何用WebGl渲染简单的平面化图形.</dd> + <dt><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL">在WebGL中使用着色器(shader)去赋予颜色</a></dt> + <dd>演示如何使用着色器给图形添加颜色.</dd> + <dt><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Animating_objects_with_WebGL">用WebGL让对象动起来</a></dt> + <dd>展示如何旋转移动物体来实现简单动画效果.</dd> + <dt><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL">使用WebGL创建3D物体</a></dt> + <dd>展示如何创建并设置一个3D物体动画 (这里使用立方体).</dd> + <dt><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL">在WebGL中使用纹理贴图(texture)</a></dt> + <dd>展示如何投射纹理贴图到物体的各个面.</dd> + <dt><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Lighting_in_WebGL">WebGL中的灯光</a></dt> + <dd>如何在WebGL上下文环境中模拟灯光效果.</dd> + <dt><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Animating_textures_in_WebGL">WebGL中的动画纹理贴图</a></dt> + <dd>展示如何让纹理贴图动起来; 在此例中, 会投射一个Ogg格式的视频在一个旋转立方体的各个面上.</dd> +</dl> diff --git a/files/zh-cn/web/api/webgl_api/tutorial/lighting_in_webgl/index.html b/files/zh-cn/web/api/webgl_api/tutorial/lighting_in_webgl/index.html new file mode 100644 index 0000000000..fcb6b665a1 --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/lighting_in_webgl/index.html @@ -0,0 +1,175 @@ +--- +title: Lighting in WebGL +slug: Web/API/WebGL_API/Tutorial/Lighting_in_WebGL +translation_of: Web/API/WebGL_API/Tutorial/Lighting_in_WebGL +--- +<p>{{WebGLSidebar("Tutorial")}} {{PreviousNext("Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL", "Web/API/WebGL_API/Tutorial/Animating_textures_in_WebGL")}}</p> + +<p>在使用灯光之前,首先我们需要了解,与定义更广泛的OpenGL不同,WebGL并没有继承OpenGL中灯光的支持。所以你只能由自己完全得控制灯光。幸运得是,这也并不是很难,本文接下来就会介绍完成灯光的基础。</p> + +<h2 id="在3D空间中模拟现实灯光">在3D空间中模拟现实灯光</h2> + +<p>在3D空间中模拟现实世界的灯光的具体原理和细节绝非本篇文章能够描述清楚的,但是对灯光模型有一定的了解对我们的学习还是很有帮助的。虽然这里没办法深入讲解,但是维基百科中的<a href="https://zh.wikipedia.org/wiki/Phong%E8%91%97%E8%89%B2%E6%B3%95">Phong着色法</a>给出了一个不错的概要介绍,其中包含了最常用的几种光照模型。</p> + +<p>光源类型可以概括成如下三种:</p> + +<p><strong>环境光 </strong>是一种可以渗透到场景的每一个角落的光。它是非方向光并且会均匀地照射物体的每一个面,无论这个面是朝向哪个方向的。</p> + +<p><strong>方向光</strong> 是一束从一个固定的方向照射过来的光。这种光的特点可以理解为好像是从一个很遥远的地方照射过来的,然后光线中的每一个光子与其它光子都是平行运动的。举个例子来说,阳光就可以认为是方向光。</p> + +<p><strong>点光源光</strong> 是指光线是从一个点发射出来的,是向着四面八方发射的。这种光在我们的现实生活中是最常被用到的。举个例子来说,电灯泡就是向各个方向发射光线的。</p> + +<p>以我们的需要来看,我们会简化光照模型,只考虑简单的方向光和环境光,不会考虑任何镜面反射和点光源。这样的话,我们只需要在我们使用的环境光上加上照射到旋转立方体的方向光就可以了。在这里可以看到之前的<a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL">旋转立方体的例子</a>。</p> + +<p>虽然可以抛开了点光源和镜面反射,但是关于方向光还是有两点需要注意一下:</p> + +<ol> + <li>需要在每个顶点信息中加入面的<strong>朝向法线</strong>。这个法线是一个垂直于这个顶点所在平面的向量。</li> + <li>需要明确方向光的传播方向,可以使用一个<strong>方向向量</strong>来定义。</li> +</ol> + +<p>接着,我们会更新顶点着色器,考虑到环境光,再考虑到方向光(方向光的作用会因为光线方向与面的夹角关系而不同),计算每一个顶点的颜色。实现这一目标的代码如下。</p> + +<h2 id="建立顶点法线">建立顶点法线</h2> + +<p>首先我们需要做的是建立一个数组来存放立方体所有顶点的法线。由于立方体是一个很简单的物体,所以很容易实现;显然如果是对复杂物体,则法线的计算方法需要更深入的研究。(注:译者调试后发现此处new WebGLFloatArray(...) 可能应该使用new Float32Array())</p> + +<pre class="brush: js">cubeVerticesNormalBuffer = gl.createBuffer(); +gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); + +var vertexNormals = [ + // Front + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + + // Back + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + + // Bottom + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + + // Right + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + + // Left + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0 +]; + +gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertexNormals), gl.STATIC_DRAW); +</pre> + +<p>现在我们应该对此非常熟悉了;创建新的buffer,将它和gl.ARRAR_BUFFER绑定在一起,然后通过调用bufferData()把我们的顶点法线数组一起传入。</p> + +<p>然后我们在drawScene()中添加代码,将法线数组和着色器的attribute绑定起来以便着色器能够获取到法线数组的信息。</p> + +<p>(此处变量 vertexNormalAttribute 应该在initShader()函数中声明, 并赋值: vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal"); gl.enableVertexAttribArray(vertexNormalAttribute);)</p> + +<pre class="brush: js">gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); +gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0); +</pre> + +<p>最后,我们(为了读者便于理解, 此处代码应该在setMatrixUniforms() 函数中添加)需要更新下代码,在着色器中建立和传递法线向量矩阵,用这个矩阵来处理当前立方体相对于光源位置法线向量的转换(注:译者调试后发现此处new WebGLFloatArray(...) 应该使用new Float32Array()):</p> + +<pre class="brush: js">var normalMatrix = mvMatrix.inverse(); +normalMatrix = normalMatrix.transpose(); +var nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix"); +gl.uniformMatrix4fv(nUniform, false, new WebGLFloatArray(normalMatrix.flatten())); +</pre> + +<h2 id="更新着色器">更新着色器</h2> + +<p>现在着色器需要的所有数据已经全部可以获取到了(或者说全部准备好了),我们需要更新下着色器本身的代码。</p> + +<h3 id="顶点着色器">顶点着色器</h3> + +<p>首先更新顶点着色器,让它给每一个基于环境光和方向光的顶点一个着色器值。让我们看下代码:</p> + +<pre class="brush: html"><script id="shader-vs" type="x-shader/x-vertex"> + attribute highp vec3 aVertexNormal; + attribute highp vec3 aVertexPosition; + attribute highp vec2 aTextureCoord; + + uniform highp mat4 uNormalMatrix; + uniform highp mat4 uMVMatrix; + uniform highp mat4 uPMatrix; + + varying highp vec2 vTextureCoord; + varying highp vec3 vLighting; + + void main(void) { + gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); + vTextureCoord = aTextureCoord; + + // Apply lighting effect + + highp vec3 ambientLight = vec3(0.6, 0.6, 0.6); + highp vec3 directionalLightColor = vec3(0.5, 0.5, 0.75); + highp vec3 directionalVector = vec3(0.85, 0.8, 0.75); + + highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0); + + highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0); + vLighting = ambientLight + (directionalLightColor * directional); + } +</script> +</pre> + +<p>一旦顶点位置计算完毕,我们就可以获得纹理对应于顶点的坐标,从而计算出顶点的阴影。</p> + +<p>我们先根据立方体位置和朝向,通过顶点法线乘以法线矩阵来转换法线。接着我们可以通过计算转换过后的法线与方向向量(即,光来自的方向)的点积来计算得出顶点反射方向光的量。如果计算出的这个值小于0,则我们把值固定设为0,因为你不会有小于0的光。</p> + +<p>当方向光的量计算完,我们可以通过获取环境光并且添加方向光的颜色和要提供的定向光的量来生成光照值(lighting value)。最终结果我们会得到一个RGB值,用于片段着色器调整我们渲染的每一个像素的颜色。</p> + +<h3 id="片段着色器">片段着色器</h3> + +<p>片段着色器现在需要根据顶点着色器计算出的光照值来更新:</p> + +<pre class="brush: js"><script id="shader-fs" type="x-shader/x-fragment"> + varying highp vec2 vTextureCoord; + varying highp vec3 vLighting; + + uniform sampler2D uSampler; + + void main(void) { + mediump vec4 texelColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); + + gl_FragColor = vec4(texelColor.rgb * vLighting, texelColor.a); + } +</script> +</pre> + +<p>和先前我们做的例子一样,我们获取纹理的颜色(原文“color of the texel”? 此处个人觉得应该就是指纹理的颜色),不同的是在设置片段颜色之前,我们将纹理的颜色乘以光照值来调整纹理以达到我们光源的效果。</p> + +<p>效果就是这样!</p> + +<p>{{EmbedGHLiveSample('webgl-examples/tutorial/sample7/index.html', 670, 510) }}</p> + +<p><a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample7">View the complete code</a> | <a href="http://mdn.github.io/webgl-examples/tutorial/sample7/">Open this demo on a new page</a></p> + +<h2 id="读者练习">读者练习</h2> + +<p>显然这是一个很简单的例子,实现了基本的每个顶点的照明。对于更高级的图形,你将可能需要实现每个像素(或者说更多像素)的照明,但这会帮助你朝着正确的方向前行。</p> + +<p>你也可以尝试光源方向颜色等等。</p> + +<p>{{PreviousNext("Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL", "Web/API/WebGL_API/Tutorial/Animating_textures_in_WebGL")}}</p> diff --git a/files/zh-cn/web/api/webgl_api/tutorial/using_shaders_to_apply_color_in_webgl/index.html b/files/zh-cn/web/api/webgl_api/tutorial/using_shaders_to_apply_color_in_webgl/index.html new file mode 100644 index 0000000000..4f2c37046c --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/using_shaders_to_apply_color_in_webgl/index.html @@ -0,0 +1,117 @@ +--- +title: 使用着色器将颜色应用于WebGL +slug: Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL +tags: + - WebGL + - 教程 +translation_of: Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL +--- +<p>{{WebGLSidebar("Tutorial")}} {{PreviousNext("Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context", "Web/API/WebGL_API/Tutorial/Animating_objects_with_WebGL")}}</p> + +<p>在<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context">之前的展示</a>中我们已经创建好了一个正方形,接下来我们要做的就是给它添加一抹色彩。添加颜色可以通过修改着色器来实现。</p> + +<h2 id="给顶点着色">给顶点着色</h2> + +<p>在GL中,物体是由一系列顶点组成的,每一个顶点都有位置和颜色信息。在默认情况下,所有像素的颜色(以及它所有的属性,包括位置)都由线性插值计算得来,自动形成平滑的渐变。我们以前的顶点着色器没有给顶点添加任何特定的颜色——在顶点着色器与片段着色器之间给每个像素着白色,于是整个正方形被渲染成纯白。</p> + +<p>现在我们假设正方形的每个顶点使用不同的颜色:红,黄,绿,白,以此渲染一个渐变的色彩。第一步,要给这些顶点建立相应的颜色。首先我们要创建一个顶点颜色数组,然后将它们存在WebGL的缓冲区中。为实现这一功能,我们在 initBuffers() 函数中加入如下代码:</p> + +<pre><code>initBuffers() { + const positionBuffer = gl.createBuffer(); + const positions = [ + 1.0, 1.0, + -1.0, 1.0, + 1.0, -1.0, + -1.0, -1.0, + ];</code> +<code> gl.bindBuffer(</code>WebGLRenderingContext<code>.ARRAY_BUFFER, positionBuffer); + gl.bufferData(</code>WebGLRenderingContext<code>.ARRAY_BUFFER, + new Float32Array(positions), + </code>WebGLRenderingContext<code>.STATIC_DRAW + ); + + const colorBuffer = gl.createBuffer(); + const colors = [ + 1.0, 1.0, 1.0, 1.0, // 白 + 1.0, 0.0, 0.0, 1.0, // 红 + 0.0, 1.0, 0.0, 1.0, // 绿 + 0.0, 0.0, 1.0, 1.0, // 蓝 + ]; + + gl.bindBuffer(</code>WebGLRenderingContext<code>.ARRAY_BUFFER, colorBuffer); + gl.bufferData(</code>WebGLRenderingContext<code>.ARRAY_BUFFER, + new Float32Array(colors), + </code>WebGLRenderingContext<code>.STATIC_DRAW + ); + + return { + position: positionBuffer, + color: colorBuffer, + }; +}</code></pre> + +<p>这段代码首先建立了一个JavaScript的数组,此数组中包含四组四值向量,每一组向量代表一个顶点的颜色。然后,创建一个WebGL缓冲区用来存储这些颜色——将数组中的值转换成WebGL所规定的浮点型后,存储在该缓冲区中。</p> + +<p>为了实际使用这些颜色,我们继续修改顶点着色器,使得着色器可以从颜色缓冲区中正确取出颜色:</p> + +<pre class="brush: html"> <script id="shader-vs" type="x-shader/x-vertex"> + attribute vec3 aVertexPosition; + attribute vec4 aVertexColor; + + uniform mat4 uModelViewMatrix; + uniform mat4 uProjectionMatrix; + + varying lowp vec4 vColor; + + void main(void) { + gl_Position = uProjectionMatrix* uModelViewMatrix * vec4(aVertexPosition, 1.0); + vColor = aVertexColor; + } + </script> +</pre> + +<p>与之前相比,这段代码的关键不同点在于:每个顶点都与一个颜色数组中的数值相连接。</p> + +<h2 id="给片段着色">给片段着色</h2> + +<p>我们先来复习一下之前构造的片段着色器:</p> + +<pre class="brush: html"> <script id="shader-fs" type="x-shader/x-fragment"> + void main(void) { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + } + </script> +</pre> + +<p>为使每个像素都得到插值后的颜色,我们只需要在此从 <code>vColor </code>变量中获取这个颜色的值:</p> + +<pre class="brush: html"> <script id="shader-fs" type="x-shader/x-fragment"> + varying lowp vec4 vColor; + + void main(void) { + gl_FragColor = vColor; + } + </script> +</pre> + +<p>这是一个非常简单的改变,每个片段只是根据其相对于顶点的位置得到一个插值过的颜色,而不是一个指定的颜色值。</p> + +<h2 id="带颜色的绘制">带颜色的绘制</h2> + +<p>接下来,我们要在 <code>initShader()</code> 中初始化颜色属性,以便着色器程序使用</p> + +<pre class="brush: js"> vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor"); + gl.enableVertexAttribArray(vertexColorAttribute); +</pre> + +<p>然后,我们便可以修改 <code>drawScene()</code> 使之在绘制正方形时使用这些颜色:</p> + +<pre class="brush: js"> gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesColorBuffer); + gl.vertexAttribPointer(vertexColorAttribute, 4, gl.FLOAT, false, 0, 0); +</pre> + +<p>{{EmbedGHLiveSample('webgl-examples/tutorial/sample3/index.html', 670, 510) }}</p> + +<p><a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample3">查看完整代码</a> | <a href="http://mdn.github.io/webgl-examples/tutorial/sample3/">在新页面中打开示例</a></p> + +<p>{{PreviousNext("Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context", "Web/API/WebGL_API/Tutorial/Animating_objects_with_WebGL")}}</p> diff --git a/files/zh-cn/web/api/webgl_api/tutorial/using_textures_in_webgl/index.html b/files/zh-cn/web/api/webgl_api/tutorial/using_textures_in_webgl/index.html new file mode 100644 index 0000000000..07b3eb2042 --- /dev/null +++ b/files/zh-cn/web/api/webgl_api/tutorial/using_textures_in_webgl/index.html @@ -0,0 +1,205 @@ +--- +title: Using textures in WebGL +slug: Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL +translation_of: Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL +--- +<p>{{WebGLSidebar("Tutorial")}} {{PreviousNext("Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL", "Web/API/WebGL_API/Tutorial/Lighting_in_WebGL")}}</p> + +<p>现在我们已经创建好了一个可以旋转的3D的立方体,接下来是时候使用贴图来代替每个面的单一的颜色了。</p> + +<h2 id="加载纹理">加载纹理</h2> + +<p>首先加入加载纹理的代码。现在我们只使用一张单一的纹理贴到立方体的6个面上,但是同样的方法可以用来加载任意数量的纹理贴图。</p> + +<div class="note"><strong>注意:</strong> 值得注意的一点是对纹理的加载同样需要遵循<a href="zh-CN/docs/Web/HTTP/Access_control_CORS">跨域访问规则</a>;也就是说你只能从允许跨域访问的网址加载你需要的纹理。下面的例子就是支持跨域访问的。</div> + +<p>加载纹理的代码如下:</p> + +<pre class="brush: js">function initTextures() { + cubeTexture = gl.createTexture(); + cubeImage = new Image(); + cubeImage.onload = function() { handleTextureLoaded(cubeImage, cubeTexture); } + cubeImage.src = "cubetexture.png"; +} + +function handleTextureLoaded(image, texture) { + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + gl.generateMipmap(gl.TEXTURE_2D); + gl.bindTexture(gl.TEXTURE_2D, null); +} +</pre> + +<p>函数 <code>initTextures()</code> 首先调用 GL {{domxref("WebGLRenderingContext.createTexture()", "createTexture()")}} 函数来创建一个GL纹理对象 cubeTexture 。为了把图片文件加载到纹理,代码首先创建了一个 <code>Image</code> 对象然后把需要当作纹理使用的图形文件加载了进来。当图片加载完成后回调函数 <code>handleTextureLoaded()</code> 就会执行。</p> + +<p>接下来为了真正地形成纹理,我们通过把新创建的纹理对象绑定到 <code>gl.TEXTURE_2D 来让它成为当前操作纹理。然后通过调用</code> {{domxref("WebGLRenderingContext.texImage2D()", "texImage2D()")}} 把已经加载的图片图形数据写到纹理。</p> + +<div class="note"><strong>注意:</strong> 在多数情况下,纹理的宽和高都必须是2的幂(如:1,2,4,8,16等等)。如果有什么特殊情况请参考下面的“<a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL#非2的幂纹理">非2的幂纹理</a>”小节。</div> + +<p>代码的接下来两行设置了纹理过滤器,过滤器用来控制当图片缩放时像素如何生成如何插值。在这个例子里,我们对图片放大使用的是线性过滤,而对图片缩小使用的是多级渐进纹理过滤。接下来我们通过调用 {{domxref("WebGLRenderingContext.generateMipMap()", "generateMipMap()")}} 来生成多级渐进纹理,接着通过给 <code>gl.TEXTURE_2D 绑定值 null 来告诉 WebGL 我们对当前纹理的操作已经结束了。</code></p> + +<h3 id="非2的幂纹理">非2的幂纹理</h3> + +<p>一般来说,宽和高都是2的幂的纹理使用是最理想的。2的幂纹理在渲染内存里的存储更高效,而且对它们的使用限制也更少。由美术工作人员生成的纹理最终在用来渲染前都应该使用放大或缩小的方式把它生成为2的幂纹理,其实事实上来说,在创作纹理之初就应该直接使用大小是2的幂的宽和高。纹理的每一边都应该是像1,2,4,8,16,32,64,128,256,512,1024或2048这样的值。当然也要注意尺寸的大小,因为虽说现在的大部分设置都已经可以支持4096像素的图片,但也不是全部;而有一些设备甚至可以支持8192或更高像素呢。</p> + +<p>有的时候从你的特定情况出发,使用2的幂纹理会比较困难。当使用到第三方的资源时,一般来说最好的方式就是先使用HTML5的画布把图片修正成2的幂然后再放到WebGL中进行渲染使用,这样一来,如果图片拉伸比较明显的话纹理坐标的值可需要适当地进行修正。</p> + +<p>但是,如果你一定要使用非2的幂纹理的话,WebGL也有原生支持,不过这些支持是受限的。当然在某些情况下使用非2的幂纹理也是很有用的,比如这张纹理刚好与你的显示器的分辨率相匹配,或者使用画布重新生成纹理的方式并不值得时。但是要特别注意的是:这种非2的幂纹理不能用来生成多级渐进纹理,而且不能使用纹理重复(重复纹理寻址等)。</p> + +<p>使用重复纹理寻址的一个例子就是使用一张砖块的纹理来平铺满一面墙壁。</p> + +<p>多级渐进纹理和纹理坐标重复可以通过调用 {{domxref("WebGLRenderingContext.texParameter()", "texParameteri()")}} 来禁用,当然首先你已经通过调用 {{domxref("WebGLRenderingContext.bindTexture()", "bindTexture()")}} 绑定过纹理了。这样虽然已经可以使用非2的幂纹理了,但是你将无法使用多级渐进纹理,纹理坐标包装,纹理坐标重复,而且无法控制设备如何处理你的纹理。</p> + +<pre class="brush: js">// gl.NEAREST is also allowed, instead of gl.LINEAR, as neither mipmap. +gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); +// Prevents s-coordinate wrapping (repeating). +gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); +// Prevents t-coordinate wrapping (repeating). +gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);</pre> + +<p>现在,当使用以上参数时,兼容WebGL的设备就会自动变得可以使用任何分辨率的纹理(当然还要考虑像素上限)。如果不使用上面这些参数的话,任何非2的幂纹理使用都会失败然后返回一张纯黑图片。</p> + +<h2 id="映射纹理到面">映射纹理到面</h2> + +<p>现在,纹理已经加载好了,而且已经可以使用了。但是在使用之前我们还要创建好纹理坐标到立方体各个面的顶点的映射关系。下面的代码通过替换之前的设置每个面颜色的代码,当然还是在 <code>initBuffers() 函数里。</code></p> + +<pre class="brush: js">cubeVerticesTextureCoordBuffer = gl.createBuffer(); +gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer); + +var textureCoordinates = [ + // Front + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + // Back + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + // Top + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + // Bottom + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + // Right + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + // Left + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0 +]; + +gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), + gl.STATIC_DRAW); +</pre> + +<p>首先,代码创建了一个GL缓存区,用来保存每个面的纹理坐标信息,然后把这个缓存区绑定到GL以方便我们写入数据。</p> + +<p><code>数组变量 textureCoordinates</code> 中定义好了与每个面上的每个顶点一一对应的纹理坐标。请注意,纹理坐标的取值范围只能从0.0到1.0,所以不论纹理贴图的实际大小是多少,为了实现纹理映射,我们要使用的纹理坐标只能规范化到0.0到1.0的范围内。</p> + +<p>纹理坐标信息给定了之后,把这个数组里的数据都写到GL缓存区,这样GL就能使用这个坐标数据了。</p> + +<h2 id="更新着色器">更新着色器</h2> + +<p>为了使用纹理来代替单一的颜色,着色器程序和着色器程序的初始化代码都需要进行修改。</p> + +<p>先让我们看一看需要加入函数 initShaders() 里的非常简单的改变:</p> + +<pre class="brush: js">textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord"); +gl.enableVertexAttribArray(textureCoordAttribute); +gl.vertexAttribPointer(texCoordAttribute, 2, gl.FLOAT, false, 0, 0); +</pre> + +<p>这段代码中我们使用包含纹理坐标信息的属性替换之前使用的顶点颜色属性。</p> + +<h3 id="顶点着色器">顶点着色器</h3> + +<p>接下来我们会修改顶点着色器代码,现在不再需要获取顶点颜色数据,而是获取纹理坐标数据。</p> + +<pre class="brush: html"><script id="shader-vs" type="x-shader/x-vertex"> + attribute vec3 aVertexPosition; + attribute vec2 aTextureCoord; + + uniform mat4 uMVMatrix; + uniform mat4 uPMatrix; + + varying highp vec2 vTextureCoord; + + void main(void) { + gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); + vTextureCoord = aTextureCoord; + } +</script> +</pre> + +<p>代码的关键更改在于不再获取顶点颜色数据转而获取和设置纹理坐标数据;这样就能把顶点与其对应的纹理联系在一起了。</p> + +<h3 id="片段着色器">片段着色器</h3> + +<p>那么片段着色器也要相应地进行更改:</p> + +<pre class="brush: html"><script id="shader-fs" type="x-shader/x-fragment"> + varying highp vec2 vTextureCoord; + + uniform sampler2D uSampler; + + void main(void) { + gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); + } +</script> +</pre> + +<p>现在的代码不会再使用一个简单的颜色值填充片段颜色,片段的颜色是通过采样器使用最好的映射方式从纹理中的每一个像素计算出来的。</p> + +<h2 id="绘制具体纹理贴图的立方体">绘制具体纹理贴图的立方体</h2> + +<p>接下来是对 <code>drawScene()</code> 函数的更改,为了使整体的代码看起来更简洁,我们去掉了让立方体位置变化的代码,现在它只会随着时间的变化进行旋转,而为了使用纹理,所要进行的代码更改确是很少的。</p> + +<p>使用下面的代码代替映射颜色到纹理的代码:</p> + +<pre class="brush: js">gl.activeTexture(gl.TEXTURE0); +gl.bindTexture(gl.TEXTURE_2D, cubeTexture); +gl.uniform1i(gl.getUniformLocation(shaderProgram, "uSampler"), 0); +</pre> + +<p>GL 最多可同时注册32张纹理;<code>gl.TEXTURE0 是第一张。我们把我们之前加载的纹理绑定到了第一个寄存器,然后着色器程序里的采样器 uSampler</code> 就会完成它的功能:使用纹理。</p> + +<p>好,现在我们的立方体就会像这样旋转起来了。</p> + +<p>{{EmbedGHLiveSample('webgl-examples/tutorial/sample6/index.html', 670, 510) }}</p> + +<p><a href="https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample6">查看完整示例代码</a> | <a href="http://mdn.github.io/webgl-examples/tutorial/sample6/">在新窗口里打开示例</a></p> + +<h2 id="关于跨域纹理">关于跨域纹理</h2> + +<p>加载WebGL纹理应该也可以说是跨域访问控制里的一个话题。为了在我们的显示内容里使用其它域名里的纹理图片,允许跨域访问也是要考虑的。可以通过查看<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">HTTP访问控制</a>来获取到更多的相关细节。</p> + +<p><a href="http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/">这篇文章</a>也对跨域加载纹理到WebGL做出了解释。而且文章里面还包含了一个使用的<a href="http://people.mozilla.org/~bjacob/webgltexture-cors-js.html">例子</a>。</p> + +<div class="note"> +<p><strong>注意:</strong>对跨域加载WebGL纹理的支持和对 image 元素的 <code>crossOrigin</code> 的属性的支持都是在 {{Gecko("8.0")}} 版本中实现的。</p> +</div> + +<p>被污染过的(只写)画布是不能拿来当作WebGL纹理来使用的。举个例子来说,当把一张跨域的图片画到一个2D的 {{ HTMLElement("canvas") }} 中时,这个画布就是“被污染过的”。</p> + +<div class="note"> +<p><strong>注意:</strong> 对 Canvas 2D 的 <code>drawImage 的跨域支持已经在 </code>{{Gecko("9.0")}} 版本实现的。这就意味着使用支持跨域的图片来污染一个2D的画布,这张画布也已经可以作为WebGL的纹理来使用了。</p> +</div> + +<div class="note"> +<p><strong>注意:</strong> 视频对跨域的支持以及 {{ HTMLElement("video") }} 元素的 <code>crossorigin</code> 属性的支持是在 {{Gecko("12.0")}} 版本中实现的。</p> +</div> + +<p>{{PreviousNext("Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL", "Web/API/WebGL_API/Tutorial/Lighting_in_WebGL")}}</p> |