使用(FBO)实现阴影效果

  • 1. demo效果
  • 2. 相关知识点
    • 2.1 阴影如何产生
    • 2.2 阴影实现原理
      • 2.2.1 准备阴影贴图
      • 2.2.2 阴影映射
      • 2.2.3 马赫带消除
  • 3. demo代码

1. demo效果

2. 相关知识点

现实生活中阴影无处不在,在三维世界中想要获得立体感,阴影必不可少,这里学习通过 阴影贴图 (shadow map) 实现阴影绘制的方法,阴影贴图又称 深度贴图(depth map)

2.1 阴影如何产生

如上图,坐标原点出有一光源,中间有一个三角形,右侧有一投影面,如果三角形上有一点P1,从光源发出一条光线经过P1点,在投影面的上投射的点是P2,这时P2点的位置就在阴影中,同理无数的光线穿过三角形会投射出一个区域,即上图中紫色区域,这个区域就是三角形的投影,在这个区域外则没有阴影

2.2 阴影实现原理

阴影实现原理并不难,分为两步:准备阴影贴图和阴影映射(阴影绘制)

2.2.1 准备阴影贴图

准备阴影贴图需要一对专门的着色器,即顶点着色器和片元着色器。在顶点着色器中需要接收一个模型视图投影矩阵,这个矩阵是以光源位置为视点位置计算的模型视图投影矩阵。在片元着色器中需要获取片元的深度值,即gl_FragCoord.z,存放在片元颜色的R分量中,但并不会绘图,而是写到阴影贴图中
着色器实现如下:

var SHADOW_VSHADER_SOURCE ='attribute vec4 a_Position;\n' +'uniform mat4 u_MvpMatrix;\n' + //声明uniform变量u_MvpMatrix,存放以光源位置为视点位置的模型视图投影矩阵'void main() {\n' +'  gl_Position = u_MvpMatrix * a_Position;\n' +'}\n';//绘制阴影的片元着色器
var SHADOW_FSHADER_SOURCE ='#ifdef GL_ES\n' +'precision mediump float;\n' +'#endif\n' +'void main() {\n' +'  gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);\n' + //将片元深度值z写入RGBA颜色的R分量'}\n';

2.2.2 阴影映射

阴影映射其实就是绘制图形,这里需要另外一对着色器,这次在顶点着色器中接收的模型视图投影矩阵,是原来场景中的模型视图投影矩阵。即以原来的视点坐标观察物体的计算出的矩阵。在片元着色器中需要将片元坐标转换为光源坐标系中的坐标,即上一步中以光源位置为视点的坐标系,然后使用当前片元的深度值与阴影贴图中记录的深度值做比较,如果前者大,则当前片元处在阴影之中。用较深暗的颜色绘制
着色器实现如下:

var VSHADER_SOURCE ='attribute vec4 a_Position;\n' + //声明attribute变量a_Position,用来存放顶点位置信息'attribute vec4 a_Color;\n' + //声明attribute变量a_Color,用来存放顶点颜色'uniform mat4 u_MvpMatrix;\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵'uniform mat4 u_MvpMatrixFromLight;\n' +'varying vec4 v_PositionFromLight;\n' +'varying vec4 v_Color;\n' +'void main() {\n' +'  gl_Position = u_MvpMatrix * a_Position;\n' +'  v_PositionFromLight = u_MvpMatrixFromLight * a_Position;\n' +'  v_Color = a_Color;\n' +'}\n';var FSHADER_SOURCE ='#ifdef GL_ES\n' +'precision mediump float;\n' +'#endif\n' +'uniform sampler2D u_ShadowMap;\n' + //声明uniform变量u_ShadowMap,存放纹理单元编号'varying vec4 v_PositionFromLight;\n' +'varying vec4 v_Color;\n' +'void main() {\n' +'  vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\n' + //转换为光源坐标系,坐标区间[-1.0,1.0]转换为[0.0,1.0]'  vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);\n' +'  float depth = rgbaDepth.r;\n' + //从R分量中获取深度值'  float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0;\n' + //判断片元在平面上是否为阴影'  gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\n' +'}\n';

2.2.3 马赫带消除

在绘制阴影的片元着色中,在做深度比较时,加了一个0.005的偏移量,之所以添加这个偏移量是为了消除马赫带,如去掉这个偏移量demo效果如下

之所以产生这种效果,是因为精度问题导致相同的值也会比较结果也会不同,详细说明一下:

  • 纹理图像的RGBA的每个分量都是8位,假设存储的值是0.1234567,实际上是31个1/256,即0.12109375
  • 计算的当前片元深度值是float类型,是16为,假设存储的值也是0.1234567,实际上是8090个1/65535,即0.12344360

这样即使相同深度也会得出不一样的结果,就会产生马赫带,为了避免产生马赫带,需要给纹理贴图的深度值添加一个偏移量,这个偏移量应略大于精度,这里0.005略大于1/256

3. demo代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title></title>
</head><body><!--通过canvas标签创建一个800px*800px大小的画布--><canvas id="webgl" width="800" height="800"></canvas><script type="text/javascript" src="./lib/cuon-matrix.js"></script><script>var VSHADER_SOURCE ='attribute vec4 a_Position;\n' + //声明attribute变量a_Position,用来存放顶点位置信息'attribute vec4 a_Color;\n' + //声明attribute变量a_Color,用来存放顶点颜色'uniform mat4 u_MvpMatrix;\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵'uniform mat4 u_MvpMatrixFromLight;\n' +'varying vec4 v_PositionFromLight;\n' +'varying vec4 v_Color;\n' +'void main() {\n' +'  gl_Position = u_MvpMatrix * a_Position;\n' +'  v_PositionFromLight = u_MvpMatrixFromLight * a_Position;\n' +'  v_Color = a_Color;\n' +'}\n';var FSHADER_SOURCE ='#ifdef GL_ES\n' +'precision mediump float;\n' +'#endif\n' +'uniform sampler2D u_ShadowMap;\n' + //声明uniform变量u_ShadowMap,存放纹理单元编号'varying vec4 v_PositionFromLight;\n' +'varying vec4 v_Color;\n' +'void main() {\n' +'  vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\n' +//转换为光源坐标系,坐标区间[-1.0,1.0]转换为[0.0,1.0]'  vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);\n' +'  float depth = rgbaDepth.r;\n' + //从R分量中获取深度值'  float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0;\n' + //判断片元在平面上是否为阴影'  gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\n' +'}\n';var SHADOW_VSHADER_SOURCE ='attribute vec4 a_Position;\n' +'uniform mat4 u_MvpMatrix;\n' + //声明uniform变量u_MvpMatrix,存放以光源位置为视点位置的模型视图投影矩阵'void main() {\n' +'  gl_Position = u_MvpMatrix * a_Position;\n' +'}\n';//绘制阴影的片元着色器var SHADOW_FSHADER_SOURCE ='#ifdef GL_ES\n' +'precision mediump float;\n' +'#endif\n' +'void main() {\n' +'  gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);\n' + //将片元深度值z写入RGBA颜色的R分量'}\n';var OFFSCREEN_WIDTH = 2048var OFFSCREEN_HEIGHT = 2048//光源坐标var LIGHT_X = 0,LIGHT_Y = 7,LIGHT_Z = 2;//创建程序对象function createProgram(gl, vshader, fshader) {//创建顶点着色器对象var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader)//创建片元着色器对象var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader)if (!vertexShader || !fragmentShader) {return null}//创建程序对象programvar program = gl.createProgram()if (!gl.createProgram()) {return null}//分配顶点着色器和片元着色器到programgl.attachShader(program, vertexShader)gl.attachShader(program, fragmentShader)//链接programgl.linkProgram(program)//检查程序对象是否连接成功var linked = gl.getProgramParameter(program, gl.LINK_STATUS)if (!linked) {var error = gl.getProgramInfoLog(program)console.log('程序对象连接失败: ' + error)gl.deleteProgram(program)gl.deleteShader(fragmentShader)gl.deleteShader(vertexShader)return null}gl.program = program//返回程序program对象return program}function loadShader(gl, type, source) {// 创建顶点着色器对象var shader = gl.createShader(type)if (shader == null) {console.log('创建着色器失败')return null}// 引入着色器源代码gl.shaderSource(shader, source)// 编译着色器gl.compileShader(shader)// 检查顶是否编译成功var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS)if (!compiled) {var error = gl.getShaderInfoLog(shader)console.log('编译着色器失败: ' + error)gl.deleteShader(shader)return null}return shader}function init() {//通过getElementById()方法获取canvas画布var canvas = document.getElementById('webgl')//通过方法getContext()获取WebGL上下文var gl = canvas.getContext('webgl')//获取绘制阴影贴图相关变量的存储地址var shadowProgram = createProgram(gl, SHADOW_VSHADER_SOURCE, SHADOW_FSHADER_SOURCE);shadowProgram.a_Position = gl.getAttribLocation(shadowProgram, 'a_Position');shadowProgram.u_MvpMatrix = gl.getUniformLocation(shadowProgram, 'u_MvpMatrix');if (shadowProgram.a_Position < 0 || !shadowProgram.u_MvpMatrix) {console.log('获取attribute变量或uniform变量存储地址失败');return;}//获取绘制阴影以外物体相关变量的存储地址var normalProgram = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);normalProgram.a_Position = gl.getAttribLocation(normalProgram, 'a_Position');normalProgram.a_Color = gl.getAttribLocation(normalProgram, 'a_Color');normalProgram.u_MvpMatrix = gl.getUniformLocation(normalProgram, 'u_MvpMatrix');normalProgram.u_MvpMatrixFromLight = gl.getUniformLocation(normalProgram, 'u_MvpMatrixFromLight');normalProgram.u_ShadowMap = gl.getUniformLocation(normalProgram, 'u_ShadowMap');if (normalProgram.a_Position < 0 || normalProgram.a_Color < 0 || !normalProgram.u_MvpMatrix ||!normalProgram.u_MvpMatrixFromLight || !normalProgram.u_ShadowMap) {console.log('获取attribute变量或uniform变量存储地址失败');return;}//初始化三角形顶点信息var triangle = initVertexBuffersForTriangle(gl)//初始化平面顶点信息var plane = initVertexBuffersForPlane(gl)if (!triangle || !plane) {console.log('初始化顶点信息失败')return}//初始化帧缓冲区对象(FBO)var fbo = initFramebufferObject(gl)if (!fbo) {console.log('初始化帧缓冲区对象失败')return}//激活0号纹理单元并绑定到纹理对象gl.activeTexture(gl.TEXTURE0)gl.bindTexture(gl.TEXTURE_2D, fbo.texture) //将帧缓冲区的颜色关联对象关联的纹理对象绑定到纹理单元// 设置canvas的背景色gl.clearColor(0, 0, 0, 1)//开启隐藏面消除gl.enable(gl.DEPTH_TEST)//创建、设置视图投影矩阵var viewProjMatrix = new Matrix4()viewProjMatrix.setPerspective(45, 1, 1, 100)viewProjMatrix.lookAt(0.0, 7.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);var viewProjMatrixFromLight = new Matrix4() //为阴影贴图创建视图投影矩阵viewProjMatrixFromLight.setPerspective(70.0, OFFSCREEN_WIDTH / OFFSCREEN_HEIGHT, 1.0, 100.0)viewProjMatrixFromLight.lookAt(LIGHT_X, LIGHT_Y, LIGHT_Z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)var mvpMatrixFromLight_t = new Matrix4(); //三角形的模型视图投影矩阵var mvpMatrixFromLight_p = new Matrix4(); //底面的模型视图投影矩阵var currentAngle = 0.0var tick = function () {currentAngle = getCurrentAngle(currentAngle) //获取当前要旋转的角度gl.bindFramebuffer(gl.FRAMEBUFFER, fbo) //绑定帧缓冲区对象,后续绘制在绑定帧缓冲区中进行gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) //设置在帧缓冲区绘制时窗口大小gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) //清空帧缓冲区//启用绘制阴影的着色器程序对象gl.useProgram(shadowProgram)//准备阴影纹理,即将三角形和平面片元写入帧缓冲区的深度关联对象drawTriangle(gl, shadowProgram, triangle, currentAngle, viewProjMatrixFromLight); //以光源位置为视点计算投影mvpMatrixFromLight_t.set(g_mvpMatrix); //恢复绘制三角形的模型视图投影矩阵drawPlane(gl, shadowProgram, plane, viewProjMatrixFromLight);mvpMatrixFromLight_p.set(g_mvpMatrix); //恢复绘制平面的模型视图投影矩阵gl.bindFramebuffer(gl.FRAMEBUFFER, null) //帧缓冲区对象解绑,解绑后绘制在默认的颜色缓冲区中进行gl.viewport(0, 0, canvas.width, canvas.height) //设置在默认的颜色缓冲区绘制时窗口大小gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) //清空颜色和深度缓冲区//启用绘制三角形和平面的着色器程序对象gl.useProgram(normalProgram);gl.uniform1i(normalProgram.u_ShadowMap, 0); //传值纹理编号//绘制三角形gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_t.elements);drawTriangle(gl, normalProgram, triangle, currentAngle, viewProjMatrix);//绘制平面及平面上三角形的阴影gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_p.elements);drawPlane(gl, normalProgram, plane, viewProjMatrix)requestAnimationFrame(tick)}tick() // 调用tick}var g_LastTime = Date.now() // 上次绘制的时间var ANGLE_SET = 30.0 // 旋转速度(度/秒)function getCurrentAngle(angle) {var now = Date.now()var elapsed = now - g_LastTime //上次调用与当前时间差g_LastTime = nowvar newAngle = angle + (ANGLE_SET * elapsed) / 1000return newAngle %= 360}//初始化帧缓冲区(FBO)function initFramebufferObject(gl) {var framebuffer, texture, depthBuffer//处理错误var error = function () {if (framebuffer) gl.deleteFramebuffer(framebuffer)if (texture) gl.deleteTexture(texture)if (depthBuffer) gl.deleteRenderbuffer(depthBuffer)return null}//创建帧缓冲区framebuffer = gl.createFramebuffer()if (!framebuffer) {console.log('创建帧缓冲区对象失败')return error()}//创建纹理对象并设置参数texture = gl.createTexture() //创建纹理对象if (!texture) {console.log('创建纹理对象失败')return error()}gl.bindTexture(gl.TEXTURE_2D, texture) //绑定纹理对象gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE,null) //纹理图像分配给纹理对象gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) //配置纹理对象参数framebuffer.texture = texture //将纹理对象关联到帧缓冲区的颜色关联对象//创建渲染缓冲区对象并设置参数depthBuffer = gl.createRenderbuffer() //创建渲染缓冲区对象if (!depthBuffer) {console.log('创建渲染缓冲区对象失败')return error()}gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer) //绑定渲染缓冲区gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) //设置渲染缓冲区尺寸//将纹理对象和渲染缓冲区对象关联到帧缓冲区对象gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer) //绑定帧缓冲区对象gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer)//检查帧缓冲区的配置状态var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER)if (gl.FRAMEBUFFER_COMPLETE !== e) { //gl.FRAMEBUFFER_COMPLETE 表示帧缓冲区对象已正确配置console.log('帧缓冲区配置不正确: ' + e.toString())return error()}//解绑帧缓冲区对象、纹理对象、渲染缓冲区对象gl.bindFramebuffer(gl.FRAMEBUFFER, null)gl.bindTexture(gl.TEXTURE_2D, null)gl.bindRenderbuffer(gl.RENDERBUFFER, null)return framebuffer}//模型矩阵、模型视图投影矩阵var g_modelMatrix = new Matrix4()var g_mvpMatrix = new Matrix4()function drawTriangle(gl, program, triangle, angle, viewProjMatrix) {//模型矩阵设置旋转角度g_modelMatrix.setRotate(angle, 0, 1, 0);draw(gl, program, triangle, viewProjMatrix);}function drawPlane(gl, program, plane, viewProjMatrix) {//模型矩阵设置旋转角度g_modelMatrix.setRotate(-45, 0, 1, 1);draw(gl, program, plane, viewProjMatrix);}function draw(gl, program, o, viewProjMatrix) {//为变量分配缓存并开启initAttributeVariable(gl, program.a_Position, o.vertexBuffer);if (program.a_Color != undefined) {initAttributeVariable(gl, program.a_Color, o.colorBuffer);}gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer);//计算模型视图投影矩阵并传值g_mvpMatrix.set(viewProjMatrix);g_mvpMatrix.multiply(g_modelMatrix);gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);gl.drawElements(gl.TRIANGLES, o.numIndices, gl.UNSIGNED_BYTE, 0);}//初始化绘制平面的信息function initVertexBuffersForPlane(gl) {//平面顶点信息var vertices = new Float32Array([3.0, -1.7, 2.5, -3.0, -1.7, 2.5, -3.0, -1.7, -2.5, 3.0, -1.7, -2.5 // v0-v1-v2-v3])//平面颜色var colors = new Float32Array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])//平面索引var indices = new Uint8Array([0, 1, 2, 0, 2, 3])var obj = new Object()//所需顶点、颜色、索引信息写入缓冲区对象obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT)obj.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT)obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE)if (!obj.vertexBuffer || !obj.colorBuffer || !obj.indexBuffer) {return null}//顶点索引数量obj.numIndices = indices.length//缓冲区对象解绑gl.bindBuffer(gl.ARRAY_BUFFER, null)gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)return obj}function initVertexBuffersForTriangle(gl) {//三角形顶点var vertices = new Float32Array([-0.8, 3.5, 0.0, 0.8, 3.5, 0.0, 0.0, 3.5, 1.8]);//颜色var colors = new Float32Array([1.0, 0.5, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 0.0]);//顶点索引数量var indices = new Uint8Array([0, 1, 2]);var obj = new Object();//所需顶点、颜色、索引信息写入缓冲区对象obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);obj.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);if (!obj.vertexBuffer || !obj.colorBuffer || !obj.indexBuffer) return null;obj.numIndices = indices.length;//缓冲区对象解绑gl.bindBuffer(gl.ARRAY_BUFFER, null);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);return obj;}function initArrayBufferForLaterUse(gl, data, num, type) {//创建缓冲区对象var buffer = gl.createBuffer()if (!buffer) {console.log('创建缓冲区对象失败')return null}//将数据写入缓冲区对象gl.bindBuffer(gl.ARRAY_BUFFER, buffer)gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)//设置num和type后续使用buffer.num = numbuffer.type = typereturn buffer}function initElementArrayBufferForLaterUse(gl, data, type) {//创建缓冲区对象var buffer = gl.createBuffer()if (!buffer) {console.log('创建缓冲区对象失败')return null}//将数据写入缓冲区对象gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer)gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW)//设置type后续使用buffer.type = typereturn buffer}//为指定变量分配缓存并开启function initAttributeVariable(gl, a_attribute, buffer) {gl.bindBuffer(gl.ARRAY_BUFFER, buffer) //将buffer绑定到缓冲区对象gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0) //缓冲区对象分配给a_attribute指定的地址gl.enableVertexAttribArray(a_attribute) //开启a_attribute指定的变量}init()</script>
</body></html>

WebGL入门(四十二)-使用(FBO)实现阴影效果相关推荐

  1. Oracle入门(十二)之SQL的DDL

    一.数据类型 Character 数据类型 Number 数据类型 Date 数据类型 Raw 和 Long Raw 数据类型 LOB 数据类型 注:Oracle数据类型详解 二.表 (1)创建表 c ...

  2. 如何选择适合你的兴趣爱好(四十二),风筝

    围城网的摇摇今天给大家带来了"如何选择适合你的兴趣爱好"系列专辑的第四十二讲--风筝.风筝是在纸鸢背上系上一条弓弦,或在纸鸢头部按一个风笛,当纸升空以后,强风通过笛,或者引起弓弦的 ...

  3. maya批量命名插件_教你玩转MAYA的四十二精华造诣(第一期)

    最近在整理文档时发现我收藏了一篇关于MAYA应用技巧的文章,突然有兴趣看了看,结果发现老版本MAYA中的某些内容很多已经无法应用于新版本.我又上网查了一下,结果发现网上好多帖子和我收藏的这篇内容基本一 ...

  4. OpenCV学习笔记(四十一)——再看基础数据结构core OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年 OpenCV学习笔记(四十三)——存取像素值操作汇总co

    OpenCV学习笔记(四十一)--再看基础数据结构core 记得我在OpenCV学习笔记(四)--新版本的数据结构core里面讲过新版本的数据结构了,可是我再看这部分的时候,我发现我当时实在是看得太马 ...

  5. 四十二、深入Java中的文件读取操作

    @Author:Runsen @Date:2020/6/8 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘课严重,专业排名 ...

  6. JavaScript学习(四十二)—利用工厂模式创建对象以及工厂模式创建对象的不足

    JavaScript学习(四十二)-利用工厂模式创建对象以及工厂模式创建对象的不足 一.利用工厂模式创建对象 工厂模式是JavaScript中的一种设计模式,它的作用是批量创建具有同种属性的对象. 格 ...

  7. 【Visual C++】游戏开发笔记四十二 浅墨DirectX教程之十 游戏输入控制利器 DirectInput专场

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本系列文 ...

  8. 多麦克风做拾音的波束_乱侃外设 篇四十二:一浪更比一浪强!HyperX Quadcast S 声浪加强版麦克风浅评...

    原标题:乱侃外设 篇四十二:一浪更比一浪强!HyperX Quadcast S 声浪加强版麦克风浅评 乱侃外设 篇四十二:一浪更比一浪强!HyperX Quadcast S 声浪加强版麦克风浅评 20 ...

  9. 【Microsoft Azure 的1024种玩法】四十二. 通过Windows Admin Center快速创建Azure Virtual Machines

    [简介] Windows Admin Center是微软开发的一套可以部署在本地基于浏览器的GUI的工具集平台,其平台可用于管理Windows相关服务器和PC机器,我们可以利用Windows Admi ...

最新文章

  1. ES6新特性:Javascript中的Reflect对象
  2. UltraEdit如何自动换行
  3. gentoo linux 分区_小白安装Gentoo Linux操作系统——磁盘分区
  4. Android应用开发控件——Gallery和ImageSwitcher
  5. Just do IT --- gulp
  6. [unity3d]自定义鼠标指针
  7. 详解k8s一个完整的监控方案(Heapster+Grafana+InfluxDB) - kubernetes
  8. 中金:基金投顾试点扩容,买方时代已至
  9. 计算机怎么禁用软件网络访问,Windows10系统下禁止软件联网的两种方法
  10. 企业运用通兑吧数字会员卡进行营销的优势
  11. 一组数据,带你读懂“2021中国民营企业500强”背后深意
  12. 编写宠物dog类python_Python编程:创建和使用类
  13. python 将字符串改成小写
  14. 使用三丰云服务器建立moon节点搭建zerotier
  15. Navicat 中mysql查询使用占位符
  16. 网络模拟环境 NS-2仿真软件简介
  17. adb的安装及环境配置
  18. 3dTile技术研究-概念详述(5)
  19. 计算机组成原理-组成篇(下)
  20. 2021高台一中高考成绩查询,2021年高考数学(理)一轮复习题型归纳与训练 专题1.3 简单的逻辑联结词、全称量词与存在量词(学生版).docx...

热门文章

  1. 黑群晖折腾之docker系列之安装宝塔面板
  2. Excel 统计符合条件不重复的个数
  3. 低成本高耐压20V移动电源方案IC首选钰泰ETA9870足5V2.4A输出
  4. MindManager 15中文版甘特图操作
  5. C++阶段03笔记02【类和对象(封装、对象的初始化和清理、对象模型和this指针、友元、运算符重载、继承、多态)】
  6. html5设计博客论文,基于HTML5的综合类博客设计与实现-计算机本科毕业论文
  7. TGO鲲鹏会中不错的两张关于领导力的PPT
  8. UniGui中使用IconCls
  9. 天天基金估值数据接口http://j4.dfcfw.com/charts/pic6/基金代码.png
  10. php字典分词,广告违禁词模块