14. WebGPU 透视投影

news/2024/11/8 17:34:08/

在上一篇文章中,介绍了如何制作 3D ,但 3D 没有任何透视效果。它使用的是所谓的“正交”视图,它有其用途,但通常不是人们说“3D”时想要的。

现在,需要添加透视图。究竟什么是透视?基本特征就是离得越远的东西显得越小。
在这里插入图片描述

看看上面的例子,远处的东西被画得更小了。给定我们当前的示例,一种使距离更远的东西看起来更小的简单方法是将剪辑空间 X 和 Y 除以 Z。

Think of it this way: If you have a line from (10, 15) to (20,15) it’s 10 units long. In our current sample it would be drawn 10 pixels long. But if we divide by Z then for example if Z is 1

这样想:如果你有一条从 (10, 15) 到 (20,15) 的线,它有 10 个单位长。在我们当前的示例中,它将被绘制为 10 像素长。但是如果我们除以 Z 那么如果 Z 是 1

10 / 1 = 10
20 / 1 = 20
abs(10-20) = 10

它将有 10 个像素长,如果 Z 为 2,则为

10 / 2 = 5
20 / 2 = 10
abs(5 - 10) = 5

5 像素长。在 Z = 3 时,它将是

10 / 3 = 3.333
20 / 3 = 6.666
abs(3.333 - 6.666) = 3.333

You can see that as Z increases, as it gets smaller, we’ll end up drawing it smaller, and therefore it will appear further way. If we divide in clip space we might get better results because Z will be a smaller number (0 to +1). If we add a fudgeFactor to multiply Z before we divide we can adjust how much smaller things get for a given distance.

你可以看到随着 Z 的增加,它变小,最终会把它画得更小,因此它会看起来更远。如果在剪辑空间中划分,可能会得到更好的结果,因为 Z 将是一个较小的数字(0 到 +1)。如果我们在除法之前添加一个 fudgeFactor 来乘以 Z,我们可以调整给定距离的东西变小的程度。

让我们试试吧。首先让将顶点着色器更改为在乘以我们的“fudgeFactor”后除以 Z。

struct Uniforms {matrix: mat4x4f,fudgeFactor: f32, //here
};struct Vertex {@location(0) position: vec4f,@location(1) color: vec4f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,
};@group(0) @binding(0) var<uniform> uni: Uniforms;@vertex fn vs(vert: Vertex) -> VSOutput {var vsOut: VSOutput;//vsOut.position = uni.matrix * vert.position;let position = uni.matrix * vert.position;let zToDivideBy = 1.0 + position.z * uni.fudgeFactor;vsOut.position = vec4f(position.xy / zToDivideBy,position.zw);vsOut.color = vert.color;return vsOut;
}

Note: By adding 1 we can set fudgeFactor to 0 and get a zToDivideBy that is equal to 1. This will let is compare when not dividing by Z because dividing by 1 does nothing.

注意:通过加 1,我们可以将 fudgeFactor 设置为 0 并得到等于 1 的 zToDivideBy 。这将在不除以 Z 时进行比较,因为除以 1 什么都不做。

还需要更新代码来设置 fudgeFactor。

  // matrix// const uniformBufferSize = (16) * 4;// matrix, fudgeFactor, paddingconst uniformBufferSize = (16 + 1 + 3) * 4;const uniformBuffer = device.createBuffer({label: 'uniforms',size: uniformBufferSize,usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,});const uniformValues = new Float32Array(uniformBufferSize / 4);// offsets to the various uniform values in float32 indicesconst kMatrixOffset = 0;const kFudgeFactorOffset = 16; //hereconst matrixValue = uniformValues.subarray(kMatrixOffset, kMatrixOffset + 16);const fudgeFactorValue = uniformValues.subarray(kFudgeFactorOffset, kFudgeFactorOffset + 1); //here...const settings = {translation: [canvas.clientWidth / 2 - 200, canvas.clientHeight / 2 - 75, -1000],rotation: [degToRad(40), degToRad(25), degToRad(325)],scale: [3, 3, 3],fudgeFactor: 0.5, //here};...const gui = new GUI();gui.onChange(render);gui.add(settings.translation, '0', 0, 1000).name('translation.x');gui.add(settings.translation, '1', 0, 1000).name('translation.y');gui.add(settings.translation, '2', -1000, 1000).name('translation.z');gui.add(settings.rotation, '0', radToDegOptions).name('rotation.x');gui.add(settings.rotation, '1', radToDegOptions).name('rotation.y');gui.add(settings.rotation, '2', radToDegOptions).name('rotation.z');gui.add(settings.scale, '0', -5, 5).name('scale.x');gui.add(settings.scale, '1', -5, 5).name('scale.y');gui.add(settings.scale, '2', -5, 5).name('scale.z');gui.add(settings, 'fudgeFactor', 0, 50); //here...function render() {...mat4.ortho(0,                   // leftcanvas.clientWidth,  // rightcanvas.clientHeight, // bottom0,                   // top1200,                // near-1000,               // farmatrixValue,         // dst);mat4.translate(matrixValue, settings.translation, matrixValue);mat4.rotateX(matrixValue, settings.rotation[0], matrixValue);mat4.rotateY(matrixValue, settings.rotation[1], matrixValue);mat4.rotateZ(matrixValue, settings.rotation[2], matrixValue);mat4.scale(matrixValue, settings.scale, matrixValue);fudgeFactorValue[0] = settings.fudgeFactor; //here

还调整了 settings 以希望能够轻松查看结果。

const settings = {// translation: [45, 100, 0],translation: [canvas.clientWidth / 2 - 200, canvas.clientHeight / 2 - 75, -1000],rotation: [degToRad(40), degToRad(25), degToRad(325)],// scale: [1, 1, 1],scale: [3, 3, 3],fudgeFactor: 10,};

这是结果。

在这里插入图片描述

如果不清楚,请将“fudgeFactor”滑块从 10.0 拖动到 0.0,以查看在我们添加除以 Z 代码之前的样子。

在这里插入图片描述

事实证明,WebGPU 采用分配给顶点着色器 @builtin(position) 的 x、y、z、w 值,并自动将其除以 w。

可以很容易地通过改变着色器来证明这一点,而不是自己做除法,把 zToDivideBy 放在 vsOut.position.w 中。

@vertex fn vs(vert: Vertex) -> VSOutput {var vsOut: VSOutput;let position = uni.matrix * vert.position;let zToDivideBy = 1.0 + position.z * uni.fudgeFactor;// vsOut.position = vec4f(//     position.xy / zToDivideBy,//     position.zw);vsOut.position = vec4f(position.xyz, zToDivideBy); //herevsOut.color = vert.color;return vsOut;
}

看看它是如何完全一样的。

在这里插入图片描述

为什么 WebGPU 自动除以 W 有用?因为现在,使用更多的矩阵魔法,我们可以只使用另一个矩阵将 z 复制到 w。

像这样的矩阵

1  0  0  0
0  1  0  0
0  0  1  0
0  0  1  0

将 z 复制到 w。您可以将这些行中的每一行视为

x_out = x_in * 1 +y_in * 0 +z_in * 0 +w_in * 0 ;
y_out = x_in * 0 +
y_in * 1 +
z_in * 0 +
w_in * 0 ;z_out = x_in * 0 +
y_in * 0 +
z_in * 1 +
w_in * 0 ;w_out = x_in * 0 +
y_in * 0 +
z_in * 1 +
w_in * 0 ;

简化后是

x_out = x_in;
y_out = y_in;
z_out = z_in;
w_out = z_in;

由于我们知道 w_in 始终为 1.0,因此我们可以将之前的加 1 添加到此矩阵中。

1  0  0  0
0  1  0  0
0  0  1  0
0  0  1  1

这会将 W 计算更改为

w_out = x_in * 0 +y_in * 0 +z_in * 1 +w_in * 1 ;

and since we know w_in = 1.0 then that’s really

因为我们知道 w_in = 1.0 那么这真的

w_out = z_in + 1;

最后,如果矩阵是这样的话,我们可以重新使用 fudgeFactor

1  0  0            0
0  1  0            0
0  0  1            0
0  0  fudgeFactor  1

意思是

w_out = x_in * 0 +y_in * 0 +z_in * fudgeFactor +w_in * 1 ;

并简化为

w_out = z_in * fudgeFactor + 1;

因此,让我们再次修改程序以仅使用矩阵。

首先把顶点着色器修改改回去,这样又简单了

struct Uniforms {matrix: mat4x4f,//fudgeFactor: f32,
};struct Vertex {@location(0) position: vec4f,@location(1) color: vec4f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,
};@group(0) @binding(0) var<uniform> uni: Uniforms;@vertex fn vs(vert: Vertex) -> VSOutput {var vsOut: VSOutput;//let position = uni.matrix * vert.position;// let zToDivideBy = 1.0 + position.z * uni.fudgeFactor;// vsOut.position = vec4f(//     position.xy / zToDivideBy,//     position.zw);vsOut position = uni.matrix * vert.position;vsOut.color = vert.color;return vsOut;
}@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}

接下来创建一个函数来生成 Z → W 矩阵。

function makeZToWMatrix(fudgeFactor) {return [1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, fudgeFactor,0, 0, 0, 1,];
}

更改代码以使用它。

 //mat4.ortho(const projection = mat4.ortho(0,                   // leftcanvas.clientWidth,  // rightcanvas.clientHeight, // bottom0,                   // top1200,                // near-1000,               // far// matrixValue,         // dst);mat4.multiply(makeZToWMatrix(settings.fudgeFactor), projection, matrixValue);mat4.translate(matrixValue, settings.translation, matrixValue);mat4.rotateX(matrixValue, settings.rotation[0], matrixValue);mat4.rotateY(matrixValue, settings.rotation[1], matrixValue);mat4.rotateZ(matrixValue, settings.rotation[2], matrixValue);mat4.scale(matrixValue, settings.scale, matrixValue);

再次注意,它是完全一样的。
在这里插入图片描述

All that was basically just to show you that dividing by Z gives us perspective and that WebGPU conveniently does this divide by Z for us.

所有这些基本上只是为了展示除以 Z 给我们提供了视角,而 WebGPU 可以方便地为我们除以 Z。

但仍然存在一些问题。例如,如果将 Z 设置为 -1100 左右,您将看到类似下面的动画

在这里插入图片描述

What’s going on? Why is the F disappearing early? Just like WebGPU clips X and Y or +1 to -1 it also clips Z. Unlike X and Y, Z clips 0 to +1. What we’re seeing here is Z < 0 in clip space.

这是怎么回事?为什么F早早消失了?就像 WebGPU 剪辑 X 和 Y 或 +1 到 -1 一样,它也剪辑 Z。与 X 和 Y 不同,Z 剪辑 0 到 +1。我们在这里看到的是裁剪空间中的 Z < 0。

在这里插入图片描述

With with divide by W in place, our matrix math + the divide by W defines a frustum. The front of the frustum is Z = 0, the back is Z = 1. Anything outside of that is clipped.

通过除以 W,我们的矩阵数学 + 除以 W 定义了一个视锥体。视锥体的前面是 Z = 0,后面是 Z = 1。外面的任何东西都被剪掉了。

frustum 截锥体

noun: 名词:

a cone or pyramid with the upper part cut off by a plane parallel to its base

上部被平行于其底面的平面截断的圆锥体或棱锥体

I could go into detail about the math to fix it but you can derive it the same way we did 2D projection. We need to take Z, add some amount (translation) and scale some amount and we can make any range we want get remapped to the -1 to +1.

我可以详细介绍数学来修复它,但您可以像我们做 2D 投影一样推导它。我们需要获取 Z,添加一些数量(平移)并缩放一些数量,我们可以将我们想要的任何范围重新映射到 -1 到 +1。

很酷的是所有这些步骤都可以在 1 个矩阵中完成。更好的是,我们将决定 fieldOfView 而不是 fudgeFactor 并计算正确的值来实现这一点。

这是一个构建矩阵的函数。

const mat4 = {...perspective(fieldOfViewYInRadians, aspect, zNear, zFar, dst) {dst = dst || new Float32Array(16);const f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewYInRadians);const rangeInv = 1 / (zNear - zFar);dst[0] = f / aspect;dst[1] = 0;dst[2] = 0;dst[3] = 0;dst[4] = 0;dst[5] = f;dst[6] = 0;dst[7] = 0;dst[8] = 0;dst[9] = 0;dst[10] = zFar * rangeInv;dst[11] = -1;dst[12] = 0;dst[13] = 0;dst[14] = zNear * zFar * rangeInv;dst[15] = 0;return dst;}

This matrix will do all our conversions for us. It will adjust the units so they are in clip space, it will do the math so that we can choose a field of view by angle and it will let us choose our Z-clipping space. It assumes there’s an eye or camera at the origin (0, 0, 0) and given a zNear and a fieldOfView it computes what it would take so that stuff at zNear ends up at Z = 0 and stuff at zNear that is half of fieldOfView above or below the center ends up with Y = -1 and Y = 1 respectively. It computes what to use for X by just multiplying by the aspect passed in. We’d normally set this to the width / height of the display area. Finally, it figures out how much to scale things in Z so that stuff at zFar ends up at Z = 1.

该矩阵将为我们完成所有转换。它会调整单位,使它们在裁剪空间中,它会进行数学计算,以便我们可以按角度选择视野,它会让我们选择我们的 Z 裁剪空间。它假设在原点 (0, 0, 0) 处有一只眼睛或相机,并给定一个 zNear 和一个 fieldOfView 它计算它需要什么,以便 zNear 的东西最终在 Z = 0 和东西在 zNear 是中心上方或下方 fieldOfView 的一半,分别以 Y = -1 和 Y = 1 结尾。它通过乘以传入的 aspect 来计算用于 X 的内容。我们通常将其设置为显示区域的 width / height 。最后,它计算出在 Z 中缩放多少东西,以便 zFar 中的东西最终达到 Z = 1 。

Here’s a diagram of the matrix in action.
这是矩阵的示意图。

在这里插入图片描述

The matrix takes the space inside the frustum and converts that to clip space. zNear defines where things will get clipped in the front and zFar defines where things get clipped in the back. Set zNear to 23 and you’ll see the front of the spinning cubes get clipped. Set zFar to 24 and you’ll see the back of the cubes get clipped.

矩阵采用视锥体内的空间并将其转换为裁剪空间。 zNear 定义了前面的内容被剪裁的位置, zFar 定义了后面的内容被剪裁的位置。将 zNear 设置为 23,您会看到旋转立方体的前部被剪裁。将 zFar 设置为 24,您会看到立方体的背面被剪裁了。

让我们在示例中使用此功能。

 const settings = {fieldOfView: degToRad(100),translation: [canvas.clientWidth / 2 - 200, canvas.clientHeight / 2 - 75, -1000],rotation: [degToRad(40), degToRad(25), degToRad(325)],scale: [3, 3, 3],//fudgeFactor: 10,};const radToDegOptions = { min: -360, max: 360, step: 1, converters: GUI.converters.radToDeg };const gui = new GUI();gui.onChange(render);gui.add(settings, 'fieldOfView', {min: 1, max: 179, converters: GUI.converters.radToDeg});gui.add(settings.translation, '0', 0, 1000).name('translation.x');gui.add(settings.translation, '1', 0, 1000).name('translation.y');gui.add(settings.translation, '2', -1400, 1000).name('translation.z');gui.add(settings.rotation, '0', radToDegOptions).name('rotation.x');gui.add(settings.rotation, '1', radToDegOptions).name('rotation.y');gui.add(settings.rotation, '2', radToDegOptions).name('rotation.z');gui.add(settings.scale, '0', -5, 5).name('scale.x');gui.add(settings.scale, '1', -5, 5).name('scale.y');gui.add(settings.scale, '2', -5, 5).name('scale.z');//gui.add(settings, 'fudgeFactor', 0, 50);...function render() {....//const projection = mat4.ortho(//    0,                   // left//    canvas.clientWidth,  // right//    canvas.clientHeight, // bottom//    0,                   // top//    1200,                // near//    -1000,               // far//);//mat4.multiply(makeZToWMatrix(settings.fudgeFactor), projection, matrixValue);
// mat4.multiply(makeZToWMatrix(settings.fudgeFactor), projection, matrixValue);const aspect = canvas.clientWidth / canvas.clientHeight;mat4.perspective(settings.fieldOfView,aspect,1,      // zNear2000,   // zFarmatrixValue,);mat4.translate(matrixValue, settings.translation, matrixValue);mat4.rotateX(matrixValue, settings.rotation[0], matrixValue);mat4.rotateY(matrixValue, settings.rotation[1], matrixValue);mat4.rotateZ(matrixValue, settings.rotation[2], matrixValue);mat4.scale(matrixValue, settings.scale, matrixValue);

There’s just one problem left. This projection matrix assumes there’s a viewer at 0,0,0 and it assumes it’s looking in the negative Z direction and that positive Y is up. Our matrices up to this point have done things in a different way. We need to put the F, which is 150 units tall, 100 units wide, and 30 units thick, in some -Z position and it needs to be far enough away that it fits inside the frustum. The frustum we’ve defined above, with zNear = 1 will only show about 2.4 units from top to bottom when an object is 1 unit away so our F will be %98 off the screen.

只剩下一个问题了。这个投影矩阵假设在 0,0,0 处有一个观察者,并且假设它在负 Z 方向看,并且正 Y 向上。到目前为止,我们的矩阵以不同的方式做事。我们需要将 150 个单位高、100 个单位宽和 30 个单位厚的 F 放在某个 -Z 位置,并且它需要离得足够远以适合平截​​头体。我们在上面定义的截锥体, zNear = 1 当对象距离 1 个单位时,从上到下仅显示大约 2.4 个单位,因此我们的 F 将离开屏幕 %98。

Playing around with some numbers I came up with these settings.

玩弄一些数字,我想出了这些设置。

const settings = {fieldOfView: degToRad(100),// translation: [canvas.clientWidth / 2 - 200, canvas.clientHeight / 2 - 75, -1000],// rotation: [degToRad(40), degToRad(25), degToRad(325)],// scale: [3, 3, 3],translation: [-65, 0, -120],rotation: [degToRad(220), degToRad(25), degToRad(325)],scale: [1, 1, 1],};

hide deleted
And, while we’re at it let’s adjust the UI settings to be more appropriate. Let’s also remove the scale to unclutter to UI a little.
而且,当我们这样做时,让我们调整 UI 设置以使其更合适。让我们也移除比例以使 UI 更整洁一些。

const gui = new GUI();gui.onChange(render);gui.add(settings, 'fieldOfView', {min: 1, max: 179, converters: GUI.converters.radToDeg});//gui.add(settings.translation, '0', 0, 1000).name('translation.x');//gui.add(settings.translation, '1', 0, 1000).name('translation.y');//gui.add(settings.translation, '2', -1400, 1000).name('translation.z');gui.add(settings.translation, '0', -1000, 1000).name('translation.x');gui.add(settings.translation, '1', -1000, 1000).name('translation.y');gui.add(settings.translation, '2', -1400, -100).name('translation.z');gui.add(settings.rotation, '0', radToDegOptions).name('rotation.x');gui.add(settings.rotation, '1', radToDegOptions).name('rotation.y');gui.add(settings.rotation, '2', radToDegOptions).name('rotation.z');//gui.add(settings.scale, '0', -5, 5).name('scale.x');//gui.add(settings.scale, '1', -5, 5).name('scale.y');//gui.add(settings.scale, '2', -5, 5).name('scale.z');

Let’s also get rid of the grid since we’re no longer in “pixel space”.
让我们也摆脱网格,因为我们不再处于“像素空间”中。

:root {--bg-color: #fff;
}
@media (prefers-color-scheme: dark) {:root {--bg-color: #000;}
}
canvas {display: block;  /* make the canvas act like a block   */width: 100%;     /* make the canvas fill its container */height: 100%;
}

就在这里。

在这里插入图片描述

We’re back to just a matrix multiply on our shader and we’re getting both a field of view and we’re able to choose our Z space.
我们回到了我们的着色器上的矩阵乘法,我们得到了一个视野并且我们能够选择我们的 Z 空间。

接下来,是相机。

为什么我们将 F 在 Z (-120) 中移动这么远?

In the other samples we had the F at (45, 100, 0) but in the last sample it’s been moved to (-65, 0, -120). Why did it need to be moved so far away?

在其他示例中,我们将 F 设置为 (45, 100, 0),但在最后一个示例中,它已移至 (-65, 0, -120)。为什么需要将它移到这么远的地方?

The reason is up until this last sample our mat4.projection function made a projection from pixels to clip space. That means the area we were displaying kinda of represented pixels. Using ‘pixels’ really doesn’t make sense in 3D since it would only represent pixels at a specific distance from the camera.

原因是在最后一个样本之前,我们的 mat4.projection 函数从像素到裁剪空间进行了投影。这意味着我们显示的区域有点代表像素。在 3D 中使用“像素”确实没有意义,因为它只代表距相机特定距离的像素。

In other words, with our new perspective projection matrix, if we tried to draw with the F with translation at 0,0,0 and rotation 0,0,0 it we’d get this

换句话说,使用我们新的透视投影矩阵,如果我们尝试使用平移为 0,0,0 且旋​​转为 0,0,0 的 F 进行绘制,我们会得到这个
在这里插入图片描述

The F has its top left front corner at the origin. The perspective projection matrix looks toward negative Z but our F is built in positive Z. The perspective projection matrix has positive Y up but our F is built with positive Z down.

F 的左上角位于原点。透视投影矩阵朝负 Z 方向看,但我们的 F 建立在正 Z 中。透视投影矩阵具有正 Y 向上但我们的 F 建立正 Z 向下。

Our new projection only sees what’s in the blue frustum. With -zNear = 1 and with a field of view of 100 degrees then at Z = -1 the frustum is only 2.38 units tall and 2.38 * aspect units wide. At Z = -2000 (-zFar) its 4767 units tall. Since our F is 150 units big and the view can only see 2.38 units when something is at -zNear we need to move it further away from the origin to see all of it.

我们的新投影只能看到蓝色截锥体中的内容。在 -zNear = 1 且视野为 100 度的情况下,在 Z = -1 时,平截头体只有 2.38 个单位高和 2.38 * 纵横比单位宽。在 Z = -2000 (-zFar) 处,它有 4767 个单位高。由于我们的 F 有 150 个单位大,当某些东西位于 -zNear 时视图只能看到 2.38 个单位,我们需要将它移到离原点更远的地方才能看到所有的东西。

Moving it -120 units in Z moves the F inside the frustum. We also rotated it to be right side up.

将它在 Z 中移动 -120 个单位会将 F 移动到平截头体内。我们还将其旋转为正面朝上。

not to scale 不按比例


http://www.ppmy.cn/news/529305.html

相关文章

在Endnote使用知云翻译阅读文献

下载知云翻译后&#xff0c;将pdf的打开方式改成知云文献翻译&#xff0c;右键pdf文件&#xff0c;打开属性&#xff0c;更改打开方式。 可能会遇到你在知云文献标记的pdf文件不能保存&#xff0c;即pdf是只读模式&#xff0c;可以这样设置&#xff0c;找到My EndNote Library…

<文献翻译>知云文献下载地址

知云文献下载地址&#xff1a; http://www.zhiyunwenxian.cn

CNKI文献pdf批量下载

1.选择文献&#xff0c;点击批量下载首先确保你所在的学校购买了知网的数据库&#xff08;一般都会购买&#xff09;&#xff0c;下载知网的e-study软件&#xff0c;选择想要下载的论文&#xff0c;导出到e-study中&#xff0c;然后点击批量下载就可以啦&#xff0c;记得更改设…

英文文献翻译神器SCITranslate V17--一键翻译整篇文献

SCITranslate是一款可以对英文文章进行全文翻译的神器&#xff0c;内置多引擎翻译&#xff0c;各类专业的复杂词汇都可以翻译出来&#xff0c;医学生也可以放心使用。而且通过SCITranslate翻译出来的文章语句简练、通顺&#xff0c;软件支持人工翻译&#xff0c;进一步提升论文…

知网一键下载PDF文献

首先&#xff0c;我们先去chrome网上应用店下载一个 Tampermonkey 插件。我已经下载好了&#xff0c;所以是评价状态。 随后&#xff0c;你到下面这个网址去install一下CNKI PDF Download&#xff0c;把这个脚本安装上&#xff0c;即可一键下载知网的PDF。 https://greasyfork…

谷歌学术——下载论文

一些同学在找论文的时候&#xff0c;在学校数据库找不到&#xff0c;因此可以使用谷歌学术来找。但是国内被墙了&#xff0c;无法访问&#xff0c;所以可以使用镜像服务器。 首先进入谷歌镜像&#xff1a; 镜像网站&#xff08;https://ac.scmor.com/&#xff09; 点击进入之后…

推荐一个文献下载神器!免费英文文献网站还能直接翻译

每到设计课题、写基金或者毕业论文的时候&#xff0c;大家想必都会查阅大量文献。但是很多文献我们就只能看个摘要&#xff0c;没法下载全文&#xff0c;只因为学校或者单位没有购买数据库。更有甚者&#xff0c;为了写论文、做科研&#xff0c;还要自己花钱去下载&#xff0c;…

开题报告、文献检索账号、文献综述、外文翻译、抄袭检测软件、论文目录,免费分享,都在这了

开题报告、文献综述、外文翻译、论文反抄袭软件、论文目录&#xff0c;就差论文正文了&#xff0c;其他都全了&#xff01;&#xff01; 反论文抄袭检查&#xff0c;吼吼&#xff0c;终于找到了 来源&#xff1a;http://likelibrary.blog.163.com/blog/static/5285285420112289…