Cocos Creator 强立体感的「视频广告墙」效果怎么做?来看看乐府互娱的尝试

本文主要是介绍Cocos Creator 强立体感的「视频广告墙」效果怎么做?来看看乐府互娱的尝试,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引言:本文为「Cocos 中文社区第4期征稿活动」参与作品。来自乐府互娱的开发者「小写a」受电梯三面广告墙的启发,尝试在 2D 项目中做出类似效果,稍加延伸,即可应用于多种游戏场景。

公元2022年3月的某一天,上海漕河泾某中心大楼内,游戏大厂的前端 Cocos Creator 程序员小写a像往常一样进入公司大楼,走进电梯的那一刻,他看到了电梯三面广告屏上正播放着视频广告,就像下图中展示的效果一样。

c024452e90c35d68918ea99415cfd48e.gif

这个三面广告墙的效果是不是类似某些 3D 跑酷游戏场景中的广告墙,具备立体感强、灵活度高、资源利用率高、消耗小等诸多优点。在游戏中,它还可以这样被应用:

91b39a280ded62f958f443fb1f1543da.gif

循环列表上的视频滑动及点选效果

于是小写a产生了一个疑问:“我能在我的 Cocos Creator 做的 2D 项目中把这种效果做出来吗?”今天,我们就一起来探索一下!

目标

要使用 Cocos Creator 在 2D 项目中实现上述广告墙的效果,需要解决的问题主要有以下2个:

  • 目标1:游戏内的视频播放。我希望视频播放节点与普通的 Node 节点一样,可以任意设置层级、大小、事件响应等。

  • 目标2:模拟近大远小的透视效果。广告墙必须具备透视效果,同时还需支持对视频画面做更多后期效果(比如模糊、蒙板、水印等)。

行动

接下来,我们来实现上面两个小目标:

  • 从目标1出发,我们需要支持视频播放的节点与普通 Node 节点一样的特性,因此首先排除了传统的播放方式:使用原生平台接口播放。

  • 从目标2出发,如果使用 cc.Sprite 组件作为播放视频的节点,就很容易在视频图像上做后期效果的处理。

综合以上两点需求,我们找到了突破口:将每帧视频数据提取出来,使用 cc.Sprite 显示,再对贴图做后期处理得到想要的效果。如此一来,需求变成了“将视频帧转换为贴图帧和后期处理”

这里就有了两种处理方式:

  • a> 将视频格式转换为 webp 的视频格式,即可按帧读取视频数据,再生成 SpriteFrame 给 cc.Sprite 显示。

  • b> 通过 ffmpeg 库按帧读取 mp4 等视频格式的帧数据,再生成 SpriteFrame 给 cc.Sprite 显示。

可以看到,以上两种方式基本类似,差别在于源视频的格式不同。这里小写a以方式a(webp 的视频格式)为例来实现目标效果——因为实际上相比 ffmpeg,webp 的实现要更简单。

实现

1ed97aaa674df072d8b1f58ad5d60b9e.jpeg

总体设计

读取 webp 的视频帧

首先,我们在 ts 中实现 webp 播放组件:WebpPlayer.ts,以下是部分核心代码:

export default WebpPlayer extends cc.Component{// 省略不重要的代码private _spriteModel:cc.Sprite = nullprivate _texture:cc.Texture2D = nullprivate _webpdecoder = nullprivate _imagearray = nullpublic static create( node:cc.Node ) : WebpPlayer {if (!cc.isValid(node)) {return null}let webp = node.getComponent(WebpPlayer)if (!webp) {webp = node.addComponent(WebpPlayer)}let sprite = node.getComponent(cc.Sprite)if (!sprite) {sprite = node.addComponent(cc.Sprite)}webp.init(sprite)return webp}protected init(sprite:cc.Sprite) {this._spriteModel = sprite}public playWebp( res:string, repeatCount:number = 1 ) : boolean {let webpAsset = cc.resources.getInfoWithPath(res, cc.Asset);if (!webpAsset) {return false;}let webpAssetUrl = cc.assetManager.utils.getUrlWithUuid(webpAsset.uuid, {isNative: true, nativeExt: '.webp'})return  this.play(webpAssetUrl, {adapterScreen:ENUMWebpAdaptScreenType.min_adapt,repeatCount:repeatCount,})}public play(webpPath:string, param:EnterParams, callback:WEBP_CALLBACK = null) : boolean {if (param) {this._adapterScreenType = param.adapterScreen || ENUMWebpAdaptScreenType.min_adaptthis._startFrame = param.startFrame || 0this._endFrame = param.endFrame || -1this._repeatCount = param.repeatCount || 1if (param.alignToScreen) {this.alignToScreen()}} else {this._repeatCount = 1}this._curRepeatIndex = 0this._webpCallback = callbackif (this._webpPath != webpPath) {this._destroyNode()}return cc.sys.isNative ? this.playForNative(webpPath) : this.playForWeb(webpPath)}// 省略不重要的代码
}
  • web:通过第三方库 libwebp.js(+demux) 来读取。(注:这里只为验证效果,未使用效率更高的未使用效率更好的 webassembly 库。)

_readyForWeb & toload(加载 webp 文件):

protected _readyForWeb(webpPath:string, callback: Function) {if (!this._webpdecoder) {let self = thislet request = new XMLHttpRequest()request.open("GET", webpPath, true)request.responseType = "arraybuffer"request.onload = function () {switch (request.status) {case 200:self.toload(request.response, callback)breakdefault:if (callback) {callback(request.status)}break}}request.send(null)} else if (callback) {callback()}
}protected toload(arrData: any, callback: Function) {if (!this._webpdecoder) {this._webpdecoder = new window['WebPDecoder']()}let response = new Uint8Array(arrData)this._imagearray = window['WebPRiffParser'](response, 0)this._imagearray['response'] = responsethis._maxFrameCount = this._imagearray['frames'].lengthif (callback) {callback()}
}

doPlayFrameWeb:读取帧数据并填充 cc.Texture2D:

protected doPlayFrameWeb( frameIndex:number ) {let frame = this._imagearray.frames[frameIndex]let response = this._imagearray['response']let heightData = [0]let widthData = [0]let rgba = this._webpdecoder.WebPDecodeRGBA(response, frame['src_off'], frame['src_size'], widthData, heightData)let data = new Uint8Array(rgba)if (data) {frame['data'] = dataframe['imgwidth'] = widthData[0]frame['imgheight'] = heightData[0]if (!this._texture) {this._texture = new cc.Texture2D()}this._texture.initWithData(frame['data'], cc.Texture2D.PixelFormat.RGBA8888, widthData[0], heightData[0])this._onRefreshTexture(frameIndex, this._texture)} else {this.onException()} // 省略其它代码
}

_onRefreshTexture:更新 SpriteFrame 数据并显示:

protected _onRefreshTexture(frameIndex: number, tex:cc.Texture2D) {let spriteFrame = new cc.SpriteFrame()spriteFrame.setTexture(tex)this._spriteModel.spriteFrame = spriteFramethis._curFrameIndex = frameIndexif (frameIndex >= this._endFrame) {this.onPlayToEnd()}// 省略其它代码}
  • native:通过 webp 库(webp, webpmux, webpdemux)来读取。

class WebpNode {protected:WebpNode();public:virtual ~WebpNode();void release();// 省略不重要的代码static WebpNode* create(const std::string& fileName);bool getFrameData(int index, std::function<void(unsigned char *, size_t)> callback);protected:bool initWithFile(const std::string& filename);private:std::vector<unsigned char*> _datas;std::vector<size_t> _lengths;uint32_t _width;uint32_t _height;cocos2d::Image* _image = nullptr;// 省略不重要的代码};

initWithFile(c++):解析 webp 文件帧数及分辨率等数据:

bool WebpNode::initWithFile(const std::string& filename)
{cocos2d::Data data = cocos2d::FileUtils::getInstance()->getDataFromFile(fullpath);if (data.isNull()){return false;}WebPData webData = { data.getBytes(), (size_t)data.getSize() };WebPDemuxer* demux = WebPDemux(&webData);_width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);_height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);WebPIterator iter;if (WebPDemuxGetFrame(demux, 1, &iter)) {do {WebPData fData = iter.fragment;unsigned char* data = new unsigned char[fData.size];memcpy(data, fData.bytes, fData.size);_datas.push_back(data);_lengths.push_back(fData.size);} while (WebPDemuxNextFrame(&iter));WebPDemuxReleaseIterator(&iter);}WebPDemuxDelete(demux);// 省略非关键代码return true;
}

getFrameData(c++):按帧读取每帧数据。由 js 层读取,callback 把每帧数据和长度返回到 js 层:

bool WebpNode::getFrameData(int index, std::function<void(unsigned char *, size_t)> callback) {if (index < 0 || index >= _datas.size()) {return false;}if (callback) {unsigned char* buff = _datas.at(index);size_t buffLen = _lengths.at(index);bool ret = _image->initWithWebpData(buff, buffLen);if (ret) {callback(_image->getData(), _image->getDataLen());}}return true;
}

实际运行时大家可能会发现,这里有两个内存热点:

  • a> 每帧解析 Image 数据时,Image 的内存默认会重新 new 一个 _data 保存贴图数据,因此这里需要重用 Image 对象和其中的 _data 缓冲两个部分,避免每帧创建一张贴图大小的缓冲数据。

bool Image::initWithWebpData(const unsigned char * data, ssize_t dataLen)
{// 省略非关键代码auto needLen = _width * _height * (config.input.has_alpha ? 4 : 3);if ( (_dataLen > 0 && _dataLen != needLen) || _dataLen < 1 || !_data) {_dataLen = 0;CC_SAFE_DELETE(_data);_data = nullptr;_dataLen = _width * _height * (config.input.has_alpha ? 4 : 3);_data = static_cast<unsigned char*>(malloc(_dataLen * sizeof(unsigned char)));}// 省略非关键代码
}
  • b> 每一帧贴图的完整数据传递到 js 层,同样会造成 js 层出现一帧贴图的大小的数据缓冲(这个对象需要等待 gc 来释放)。因此这里也需要重用这段缓冲,否则播放视频带来的内存消耗会大幅增长。

static se::Value* s_sharedValue = nullptr;
static bool js_cocos2dx_WebpNode_getFrameData(se::State& s)
{// 省略非关键代码auto lambda = [=](unsigned char* szBuff, size_t size) -> void {se::AutoHandleScope hs;se::Value dataVal;CC_UNUSED bool ok = true;se::ValueArray args;se::HandleObject retObj(se::Object::createPlainObject());if (s_sharedValue) {se::Object* valueObj = s_sharedValue->toObject();v8::Local<v8::Object> obj = valueObj->_getJSObject();v8::Local<v8::TypedArray> arrBuf = v8::Local<v8::TypedArray>::Cast(obj);v8::ArrayBuffer::Contents content = arrBuf->Buffer()->GetContents();uint8_t* ptr = (uint8_t*)content.Data() + arrBuf->ByteOffset();size_t byteLength = content.ByteLength();if (byteLength < size) {delete s_sharedValue;s_sharedValue = nullptr;}else {memset(ptr, 0, byteLength);memcpy(ptr, szBuff, size);}}if (!s_sharedValue) {s_sharedValue = new se::Value();cocos2d::Data data;data.fastSet(szBuff, size);Data_to_seval(data, s_sharedValue);data.takeBuffer();}retObj->setProperty("data", *s_sharedValue);args.push_back(se::Value(retObj));se::Value rval;se::Object* thisObj = jsThis.isObject() ? jsThis.toObject() : nullptr;se::Object* funcObj = jsFunc.toObject();bool succeed = funcObj->call(args, thisObj, &rval);if (!succeed) {se::ScriptEngine::getInstance()->clearException();}};// 省略非关键代码
}
SE_BIND_FUNC(js_cocos2dx_WebpNode_getFrameData)

WebpPlayer 按帧读取数据并填充 cc.Texture2D:

protected doPlayFrameNative(frameIndex:number) {let self = thisif (!this._videoNative.getFrameData(frameIndex, function (buffList: any) {let buff = buffList.datalet pixelFormat = self._videoNative.pixelFormat()if (!self._image) {self._image = new Image(self._width, self._height)} else {self._image.width = self._widthself._image.height = self._height}let image = self._imageimage._data = buffimage._glFormat = self._glFormatimage._glInternalFormat = self._glInternalFormatimage._glType = self._glTypeimage._numberOfMipmaps = 0image._compressed = falseimage._bpp = self._bppimage._premultiplyAlpha = falseimage._alignment = 1image.complete = trueif (!self._texture) {self._texture = new cc.Texture2D()}self._texture.initWithData(image, pixelFormat, self._width, self._height)self._onRefreshTexture(frameIndex, self._texture)})) {this.onException()}
}

以上即完成双平台的 webp 读取和显示过程。

模拟中间广告墙的显示效果

在界面中添加三组 Sprite 节点及其外框 Sprite 节点用于显示三面广告墙的视频播放。

c447c9dc7e06de0a2df934fa3f40a093.png

显示三面广告墙的节点及视频外框

添加中间视频的播放代码:

let leftWebp = WebpPlayer.create(this._video_middle_Spr.node)
leftWebp.playWebp("advert-board/videos/game-demo", -1)

即可得到中间广告墙的播放效果:

88a0d69433c3466dde2a6e571771e50e.gif

模拟侧面广告墙的显示效果

尝试一:修改 uv 映射

通过修改 uv 映射,我们可以把四边形映射成左梯形和右梯形(假设显示效果为等腰梯形),即可模拟出电梯左面和右面的透视效果。这种方法比较简单,我们先来尝试一下,具体图示如下:

c5d4f44f8cbe9da9588939e0ddd9c1ad.png

a> 创建 board.effect, board.mtl(绑定 board.effect):

b8c7a7b5879e99112f1f1b8667a0d512.png

b> 修改一下 board.effect 中的部分代码:

CCEffect %{techniques:- passes:- vert: vsfrag: fsblendState:targets:- blend: truerasterizerState:cullMode: noneproperties:texture: { value: white }offset: { value: 0.1, editor: {range: [0.0, 0.6]} }transFlag: { value: 1.0}
}%CCProgram fs %{// 省略其它代码void main () {vec4 o = vec4(1, 1, 1, 1);vec2 uv = v_uv0;if (transFlag > 0.0)uv.y = uv.y + (uv.y - 0.5)*uv.x*offset;else uv.y = uv.y + (uv.y - 0.5)*(1.0 - uv.x)*offset;#if USE_TEXTURECCTexture(texture, uv, o);#endifif (uv.y < 0.0 || uv.y > 1.0) {o.a = 0.0;}o *= v_color;ALPHA_TEST(o);#if USE_BGRAgl_FragColor = o.bgra;#elsegl_FragColor = o.rgba;#endif}}%

属性说明:

transFlag :> 0 时变换为右梯形,否则变换为左梯形。

offset :梯形的顶边相对底边的 v 值差值 /2,范围【0,0.6】。

c> 把用来显示视频贴图帧的 cc.Sprite 组件的 Material 数组的0下标位置赋值为 board.mtl。

2f9da68dd2a4be857e69429bc8168e3e.png

d> 为右侧视频节点设置 transFlag 及 offset 参数,并播放视频:

let rightMaterial = this._video_right_Spr.getMaterial(0)
rightMaterial.setProperty("transFlag", -1.0)
rightMaterial.setProperty("offset", 0.5)let rightFrameMaterial = this._frame_right.getMaterial(0)
rightFrameMaterial.setProperty("transFlag", -1.0)
rightFrameMaterial.setProperty("offset", 0.5)let rightWebp = WebpPlayer.create(this._video_right_Spr.node)
rightWebp.playWebp("advert-board/videos/game-demo", -1)

得到的右侧广告墙的效果如下:

a3786fe619aba4bc9067293bd8de86eb.png

右侧静态效果

7acfb4cacd302c3bc17aab77ed66dff2.gif

整体动态效果

效果是差不多了,不过细看之下发现,侧面两个广告墙的画面发生了扭曲:画面顶边和底边变成曲线了。

仔细分析发现了原因:

uv.y = uv.y + ( uv.y - 0.5)* uv.x *offset;

这个计算的结果是非线性的,因此 uv 映射的 y 值就是非线性的,上下两边出现曲线也就在情理之中。

TIPS:到这里,可能大家会有一个疑问:是不是可以把 uv 的映射放到 vs 中,或者使用顶点映射?我的确试过,错得更离谱,这里就不展开了,感兴趣的小伙伴可以尝试一下。

正当小写a同学一愁莫展的时候,阿蓝同学假装恰好路过(没错,就是上次写过那篇《用 2D 相机实现 3D 翻转》的乐府-阿蓝),“Waooh! So cool!,这是什么效果?”。此处省略1000字。然后小写a开心的找到了新方案。

在 2D 相机中实现 3D 翻转

按照《用 2D 相机实现 3D 翻转》文章的方案,最终实现了开头示例中的效果。具体修改如下:

a> 使用新的修改 board.effect:删除 transFlag, offset 属性,增加 map_vp 属性:

mat_vp: {value:[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]}

恢复 fs 中的代码并修改 vs:

CCProgram vs %{precision highp float;#include <cc-global>#include <cc-local>in vec3 a_position;in vec4 a_color;out vec4 v_color;in vec2 a_uv0;out vec2 v_uv0;uniform UNIFORM{mediump mat4 mat_vp;};void main () {vec4 pos = vec4(a_position, 1);#if CC_USE_MODELpos = mat_vp * cc_matWorld * pos;#elsepos = mat_vp * pos;#endifv_uv0 = a_uv0;v_color = a_color;gl_Position = pos;}
}%

b> 对视频播放节点调用以下方法做初始化:

public static setVPMatToNode(node:cc.Node) {//计算设备的宽度/高度let aspect = (cc.view as any)._viewportRect.width / (cc.view as any)._viewportRect.height//得到视图矩阵matViewlet matView:any = cc.mat4()let matViewInv:any = cc.mat4()cc.Camera.main.node.getWorldRT(matViewInv)cc.Mat4.invert(matView, matViewInv)//得到透视矩阵let matP:any = cc.mat4()let fovy = Math.PI / 4cc.Mat4.perspective(matP, fovy, aspect, 1, 2500)//VP = 透视矩阵*视图矩阵let matVP = cc.mat4()cc.Mat4.multiply(matVP, matP, matView);let arr = new Float32Array(16);for (let i=0;i<16;i++){arr[i]= matVP.m[i]}let material = node.getComponent(cc.Sprite).getMaterial(0)material.setProperty("mat_vp", arr)
}

对视频播放节点调用 setVPMatToNode 方法设置 map_vp 属性,并设置旋转角度:

public onLoad() {// 中间广告屏节点PerspectiveCamera.setVPMatToNode(this._video_middle_Spr.node)PerspectiveCamera.setVPMatToNode(this._frame_middle.node)// 左面广告屏节点PerspectiveCamera.setVPMatToNode(this._video_left_Spr.node)this._video_left_Spr.node.rotationY = 90PerspectiveCamera.setVPMatToNode(this._frame_left.node)// 右面广告屏节点PerspectiveCamera.setVPMatToNode(this._video_right_Spr.node)this._video_right_Spr.node.rotationY = -90PerspectiveCamera.setVPMatToNode(this._frame_right.node)
}

c> 把视频播放节点都设置为 3D,并设置好深度值:

4c525b51f841e20552992ef96fb0c9f5.png

bef65d9b63eed6bd96113fb21227fd8c.png

三个节点上的 3D 属性和深度值设置

至此大功告成,我们得到了本文开头展示的最终效果:

047af1d26348feeb0568120978eef55c.gif


点击文末【阅读原文】前往论坛专贴查看详细实现方案,和作者交流、讨论!更多干货,欢迎关注「乐府札记」公众号。

往期精彩

9a55705a0182e24a49da16b39161539f.png

3b735876faa7a74921425f2d87782a08.png

07089c02358f270431902954e6c8aaa8.png

e2b51443fc3673e1252dc357a8b80c1f.gif

这篇关于Cocos Creator 强立体感的「视频广告墙」效果怎么做?来看看乐府互娱的尝试的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/289352

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

AI绘图怎么变现?想做点副业的小白必看!

在科技飞速发展的今天,AI绘图作为一种新兴技术,不仅改变了艺术创作的方式,也为创作者提供了多种变现途径。本文将详细探讨几种常见的AI绘图变现方式,帮助创作者更好地利用这一技术实现经济收益。 更多实操教程和AI绘画工具,可以扫描下方,免费获取 定制服务:个性化的创意商机 个性化定制 AI绘图技术能够根据用户需求生成个性化的头像、壁纸、插画等作品。例如,姓氏头像在电商平台上非常受欢迎,

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

防近视护眼台灯什么牌子好?五款防近视效果好的护眼台灯推荐

在家里,灯具是属于离不开的家具,每个大大小小的地方都需要的照亮,所以一盏好灯是必不可少的,每个发挥着作用。而护眼台灯就起了一个保护眼睛,预防近视的作用。可以保护我们在学习,阅读的时候提供一个合适的光线环境,保护我们的眼睛。防近视护眼台灯什么牌子好?那我们怎么选择一个优秀的护眼台灯也是很重要,才能起到最大的护眼效果。下面五款防近视效果好的护眼台灯推荐: 一:六个推荐防近视效果好的护眼台灯的

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

速盾高防cdn是怎么解决网站攻击的?

速盾高防CDN是一种基于云计算技术的网络安全解决方案,可以有效地保护网站免受各种网络攻击的威胁。它通过在全球多个节点部署服务器,将网站内容缓存到这些服务器上,并通过智能路由技术将用户的请求引导到最近的服务器上,以提供更快的访问速度和更好的网络性能。 速盾高防CDN主要采用以下几种方式来解决网站攻击: 分布式拒绝服务攻击(DDoS)防护:DDoS攻击是一种常见的网络攻击手段,攻击者通过向目标网

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试