纹理加载后是全白的_《前端图形学从入门到放弃》2.5 画皮:纹理贴图

cc928f620b00f6ee851c8e4e6344b187.png

上一篇结尾,我们说过要进入3D的世界。但实际上我们还有一件事没有说这就是纹理贴图。某百科对纹理贴图的定义:

纹理贴图,又称 材质贴图,在计算机图形学中是把存储在内存里的位图包裹到3D渲染物体的表面。纹理贴图给物体提供了丰富的细节,用简单的方式模拟出了复杂的外观。

例如我们给一个物体贴上一个砖块的纹理他就看起来像一面墙:

e301c6a111d2236a1371cc8f38f8b8c8.png
图片来源:网络,侵删

添加纹理

需要注意的是无论物体是三维中如何复杂的物体,它都是由一个个几何平面组成的。

711095e43d24e823d48cf34b9aff972b.png
图片来源:网络,侵删

我们的问题贴图也是贴在这个几何平面之上。一张贴图的那个部分贴到空间中的哪一个平面上,这就涉及到纹理坐标和空间坐标的一个映射。

特别的纹理坐标系分别用U与V表示:

a7185fad8b3bd95b9c7db70fe9165b79.png
图片来源:维基百科

本章我们不会实现这么复杂的效果,目前我们能实现在一个正方形上贴图即可:

e445a70f7436e27988c16b933568243f.png
最终效果

修改作色器

所以我们要在顶点作色器中再添加两个变量uvCoordinatefragColor,uvCoordinate用来存放uv坐标,fragColor用来把uv坐标传递给片段作色器:

// 顶点作色器代码
attribute vec3 vertPosition;
attribute vec2 uvCoordinate;
varying vec2 fragColor;
void main() {
   fragColor = uvCoordinate;
   fragColor.y = 1.0 - fragColor.y;
   gl_Position = vec4(vertPosition,1.0);
}
这里需要注意的是由于uv坐标是左下角是(0,0)点与图片的坐标是相反的所以在作色器中才有fragColor.y = 1.0 - fragColor.y;的语法

片段作色器中的fragColor就是由顶点作色器传递过来的,这里我们还声明了一个全局变量sampler用来存放纹理,webgl的WebGLRenderingContext.texImage2D() 方法指定了二维纹理图像。详情可以访问mdn文档查看。

// 片段作色器
precision mediump float;
varying vec2 fragColor;
uniform sampler2D sampler;
void main() {
    gl_FragColor = texture2D(sampler, fragColor);
}

现在作色器准备完毕了,我们继续

创建buffer

我们需要在一个正方形上贴上一张贴图。所以我们需要空间定义两个三角形组成一个正方形。让后在告诉webgl每一个顶点所对应的uv坐标。根据前篇所述的方法我们创建这个buffer:

var positionData = [
    -0.5, 0.5, .5,
     0.5, 0.5, .5,
    -0.5, -0.5, 0.75,
    -0.5, -0.5, 0.75,
     0.5, 0.5, .5,
     0.5, -0.5, 0.75,
];
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positionData), gl.STATIC_DRAW);
var vertPosition = gl.getAttribLocation(program, 'vertPosition');
gl.vertexAttribPointer(
   vertPosition, 
   3, 
   gl.FLOAT, 
   gl.FALSE,
   0, 
   0 
);
gl.enableVertexAttribArray(vertPosition);

var uvData = [
   0.0, 1.0,
   1.0, 1.0,
   0.0, 0.0,
   0.0, 0.0,
   1.0, 1.0,
   1.0, 0.0
];
var uvCoordinate = gl.getAttribLocation(program, 'uvCoordinate');
   var uvBuffer = gl.createBuffer();
   gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvData), gl.STATIC_DRAW);
   gl.vertexAttribPointer(
     uvCoordinate, 
     2, 
     gl.FLOAT, 
     gl.FALSE,
     0, 
     0 
); 
gl.enableVertexAttribArray(uvCoordinate);

只要定义了每个顶点的属性如uv,法线,颜色,那么对于顶点围城的图形中的任意一点。片段作色器都能通过线性插值的方法求出对应的uv,法线,颜色,实现平滑过度。

绑定纹理

var textureBuffer = gl.createTexture();
 gl.bindTexture(gl.TEXTURE_2D, textureBuffer);
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);  //纹理坐标水平填充 s  gl.REPEAT (默认值),gl.CLAMP_TO_EDGE, gl.MIRRORED_REPEAT.
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);  // 纹理坐标垂直填充 t gl.REPEAT (默认值),gl.CLAMP_TO_EDGE, gl.MIRRORED_REPEAT.
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);  // 纹理缩小滤波器  gl.LINEAR, gl.NEAREST, gl.NEAREST_MIPMAP_NEAREST, gl.LINEAR_MIPMAP_NEAREST, gl.NEAREST_MIPMAP_LINEAR (默认值), gl.LINEAR_MIPMAP_LINEAR.
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);  // 纹理放大滤波器  gl.LINEAR (默认值), gl.NEAREST.
 gl.texImage2D(
    gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
    gl.UNSIGNED_BYTE, document.getElementById('crate-image')
);

与attribute不同,纹理buffer需要使用gl.createTexture方法创建。由于图片加载是操作异步的,我们将图片放在了一个img标签内,并在window.onload 中执行。

....
<body>
  <img id="crate-image" src="./texture.jpg" width="0" height="0" />
  <canvas id="canvas"></canvas>
  <script>
   window.onload = function () {
     // ......
   }
</script>

当然也可以用var imgNode = new Image()创建图片,并在imgNode.onload方法中执行buffer的绑定。

var imgNode = new Image();
imgNode.src = "example://site.com";
imgNode.onloade = function(){
    // .....
}

这样最终得到如图结果:

c8ae07cc2efc55bd3a49188a24fc032b.png

开始你的表演

当然贴图的功能不仅如此,比如你可以切换通道来使贴图变色:

<script id="fragment-shader-2d" type="x-shader/x-fragment">
    precision mediump float;
    varying vec2 fragColor;
    uniform sampler2D sampler;
    void main() {
       gl_FragColor = texture2D(sampler, fragColor).grba;  
    }
  </script>

上面的片段作色器的红通道与绿通道互换了。

6c38f538bd76bac0882d910f504a3dca.png

我们还能对图片进行更复杂的操作。

模糊

7b789a0d82421df9888b7c1e9b63a659.png
模糊后的图片

举个例子当我们要对一张图片进行模糊是,我们采用如下模糊算法,讲每个像素点表示为周围5个像素点的平均值。此时我们需要知道一个像素点有多大,我们可以在片段作色器中声明一个变量vec2 u_textureSize来表示图片的像素,那么它对应到uv坐标上的大小就是vec2(1.0)/u_textureSize。

这是GLSL的简化写法相当于 vec2(1.0/u_textureSize.x,1.0/u_textureSize.y);

在js代码中我们取出u_textureSize,并对其进行赋值:

var imgNode = document.getElementById('crate-image');
var u_textureSize = gl.getUniformLocation(program, "u_textureSize");
gl.uniform2fv(u_textureSize, new Float32Array([imgNode.width, imgNode.height]));

gl.uniform2fv的api可以参考mdn文档

// a_ 代表属性,值从缓冲中提供;
// u_ 代表全局变量,直接对着色器设置;
// v_ 代表可变量,是从顶点着色器的顶点中插值来出来的

卷积内核

现在任意一个像素显示的值,是由原始像素自己和周围的八个像素点决定了。那么每个像素贡献是多少?我们把他们存在一个矩阵之中这就是卷积内核,如上模糊操作,我们使用的矩阵就是

var edgeDetectKernel = [
 1, 1, 1,
 1, 1, 1,
 1, 1, 1
];

将卷积内核传入作色器后

var u_kernel = gl.getUniformLocation(program, "u_kernel");
 gl.uniform1fv(u_kernel,edgeDetectKernel);

修改作色器:

uniform float u_kernel[9];
void main() {
    gl_FragColor = (
       texture2D(sampler, fragColor + onePixel*vec2(-1.0,-1.0))*u_kernel[0] + 
       texture2D(sampler, fragColor + onePixel*vec2(.0,-1.0))*u_kernel[1] + 
       texture2D(sampler, fragColor + onePixel*vec2(1.0,-1.0))*u_kernel[2] + 
       texture2D(sampler, fragColor + onePixel*vec2(-1.0,.0))*u_kernel[3] + 
       texture2D(sampler, fragColor + onePixel*vec2(.0,.0))*u_kernel[4] + 
       texture2D(sampler, fragColor + onePixel*vec2(1.0,.0))*u_kernel[5] + 
       texture2D(sampler, fragColor + onePixel*vec2(-1.0,1.0))*u_kernel[6] + 
       texture2D(sampler, fragColor + onePixel*vec2(.0,1.0))*u_kernel[7] + 
       texture2D(sampler, fragColor + onePixel*vec2(1.0,1.0))*u_kernel[8]);
}

但这样会造成颜色越来越亮,所以我们还需要定义u_kernelWeight

function computeKernelWeight(kernel) {
    var weight = kernel.reduce(function(prev, curr) {
        return prev + curr;
    });
    return weight <= 0 ? 1 : weight;
}

var u_kernel = gl.getUniformLocation(program, "u_kernel");
var u_kernelWeight = gl.getUniformLocation(program, "u_kernelWeight");
gl.uniform1fv(u_kernel,edgeDetectKernel);
gl.uniform1f(u_kernelWeight,computeKernelWeight(edgeDetectKernel));

在作色器中除以u_kernelWeight:

vec4 colorSum = 
       texture2D(sampler, fragColor + onePixel*vec2(-1.0,-1.0))*u_kernel[0] + 
       texture2D(sampler, fragColor + onePixel*vec2(.0,-1.0))*u_kernel[1] + 
       texture2D(sampler, fragColor + onePixel*vec2(1.0,-1.0))*u_kernel[2] + 
       texture2D(sampler, fragColor + onePixel*vec2(-1.0,.0))*u_kernel[3] + 
       texture2D(sampler, fragColor + onePixel*vec2(.0,.0))*u_kernel[4] + 
       texture2D(sampler, fragColor + onePixel*vec2(1.0,.0))*u_kernel[5] + 
       texture2D(sampler, fragColor + onePixel*vec2(-1.0,1.0))*u_kernel[6] + 
       texture2D(sampler, fragColor + onePixel*vec2(.0,1.0))*u_kernel[7] + 
       texture2D(sampler, fragColor + onePixel*vec2(1.0,1.0))*u_kernel[8];
gl_FragColor = vec4((colorSum/u_kernelWeight).rgb,1.0);

当然使用不同的卷积内核我们可以得到人脑无法想象的效果,比如浮雕效果的卷积内核:

var edgeDetectKernel = [
 -2, -1, 0,
 -1, 1, 1,
 0, 1, 2
];

c13e387119d55da91bb133a82595d4bd.png

比如边界检测效果的卷积内核:

var edgeDetectKernel = [
 0, 1, 0,
 1, -4, 1,
 0, 1, 0
];

fb05f0e991a9c84d9ec27635326cd53e.png

下期预告

下期我们真的要进入3d世界了,讨论下3d世界中的三个变换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值