golang,OpenGL,计算机图形学(三)

news2024/11/18 1:48:27

代码仓库

https://github.com/phprao/go-graphic

颜色

光源照射到物体上,一部分颜色被吸收,另一部分无法吸收的被反射到人眼,于是呈现出了颜色。

当我们把光源的颜色与物体的颜色值相乘(而不是点乘),所得到的就是这个物体所反射的颜色。由此,我们可以定义物体的颜色为物体从一个光源反射各个颜色分量的大小。

要知道,两个向量的点乘结果是一个标量,在GLSL中两个向量相乘使用A * B,得到的是一个向量,即各个分量相乘的结果,而在GLSL中的点乘是dot(A, B),这个得到的是标量。

基础光照

冯氏光照模型(Phong Lighting Model)

由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。

在这里插入图片描述

  • 环境光照(Ambient Lighting):即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
  • 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。
  • 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。
环境关照

光通常都不是来自于同一个光源,而是来自于我们周围分散的很多光源,即使它们可能并不是那么显而易见。光的一个属性是,它可以向很多方向发散并反弹,从而能够到达不是非常直接临近的点。所以,光能够在其它的表面上反射,对一个物体产生间接的影响。考虑到这种情况的算法叫做全局照明(Global Illumination)算法,但是这种算法既开销高昂又极其复杂。

由于我们现在对那种又复杂又开销高昂的算法不是很感兴趣,所以我们将会先使用一个简化的全局照明模型,即环境光照。正如你在上一节所学到的,我们使用一个很小的常量(光照)颜色,添加到物体片段的最终颜色中,这样子的话即便场景中没有直接的光源也能看起来存在有一些发散的光。

把环境光照添加到场景里非常简单。我们用光的颜色乘以一个很小的常量环境因子,再乘以物体的颜色,然后将最终结果作为片段的颜色:

#version 410
    
out vec4 frag_colour;

uniform vec3 lightColor;
uniform vec3 objectColor;

void main() {
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;
    frag_colour = vec4(ambient * objectColor, 1.0);
}
漫反射光照

环境光照本身不能提供最有趣的结果,但是漫反射光照就能开始对物体产生显著的视觉影响了。漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。

在这里插入图片描述

我们知道两个单位向量的夹角越小,它们点乘的结果越倾向于1。当两个向量的夹角为90度的时候,点乘会变为0。这同样适用于θθ,θθ越大,光对片段颜色的影响就应该越小。

关于向量的 点乘

点乘得到的是标量,我们可以用它计算光线对片段颜色的影响。不同片段朝向光源的方向的不同,这些片段被照亮的情况也不同。

所以,计算漫反射光照需要什么?

  • 法向量:一个垂直于顶点表面的向量。
  • 定向的光线:作为光源的位置与片段的位置之间向量差的方向向量。为了计算这个光线,我们需要光的位置向量和片段的位置向量。

法向量是一个垂直于顶点表面的(单位)向量。由于顶点本身并没有表面(它只是空间中一个独立的点),我们利用它周围的顶点来计算出这个顶点的表面。我们能够使用一个小技巧,使用叉乘对立方体所有的顶点计算法向量,但是由于3D立方体不是一个复杂的形状,所以我们可以简单地把法线数据手工添加到顶点数据中。

计算每一个顶点漫反射光,我们需要知道以下数据:

  • 光源的位置:固定值,通过uniform传入
  • 每一个顶点在世界坐标系中的位置:将Model矩阵乘以顶点的原始坐标,而不是经过投影和视角转换之后的gl_Position,这一点需要注意,因为光源和物体的位置是固定的,每个顶点算出来的光线值就是固定的,它不会随着投影和视角的变换而变化。
  • 每一个顶点的(单位)法向量的值:暂时手动填入到顶点属性中。

接下来计算光源到顶点的向量

vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

当计算光照时我们通常不关心一个向量的模长或它的位置,我们只关心它们的方向。所以,几乎所有的计算都使用单位向量完成,因为这简化了大部分的计算(比如点乘)。所以当进行光照计算时,确保你总是对相关向量进行标准化,来保证它们是真正地单位向量。忘记对向量进行标准化是一个十分常见的错误。

下一步,我们对normlightDir向量进行点乘,计算光源对当前片段实际的漫反射影响。结果值再乘以光的颜色,得到漫反射分量。两个向量之间的角度越大,漫反射分量就会越小:

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

最后跟环境光相加

frag_colour = vec4((ambient + diffuse) * objectColor, 1.0);

在这里插入图片描述

最后还有一个问题,顶点的坐标是乘以了Model矩阵,但是法向量却没有做处理,也就是说法向量并没有转换到世界空间中来,这一块的原理比较复杂,可以 查看,结论就是:

fNormal = mat3(transpose(inverse(model))) * vNormal;

矩阵求逆是一项对于着色器开销很大的运算,因为它必须在场景中的每一个顶点上进行,所以应该尽可能地避免在着色器中进行求逆运算。以学习为目的的话这样做还好,但是对于一个高效的应用来说,你最好先在CPU上计算出法线矩阵,再通过uniform把它传递给着色器(就像模型矩阵一样)。

normalModel := model1.Inv().Transpose().Mat3()

gl.UniformMatrix3fv(gl.GetUniformLocation(program1, gl.Str("normalModel\x00")), 1, false, &normalModel[0])

效果没什么差别,这是因为我们没有对物体进行任何缩放操作,所以我们并不真的需要使用一个法线矩阵,而是仅以模型矩阵乘以法线就可以。但是如果你会进行不等比缩放,使用法线矩阵去乘以法向量就是必须的了。

镜面光照

和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向,例如玩家是从什么方向看向这个片段的。镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方。

在这里插入图片描述

我们通过根据法向量翻折入射光的方向来计算反射向量。然后我们计算反射向量与观察方向的角度差,它们之间夹角越小,镜面光的作用就越大。由此产生的效果就是,我们看向在入射光在表面的反射方向时,会看到一点高光。

观察向量是我们计算镜面光照时需要的一个额外变量,我们可以使用观察者的世界空间位置和片段的位置来计算它。之后我们计算出镜面光照强度,用它乘以光源的颜色,并将它与环境光照和漫反射光照部分加和。

我们选择在世界空间进行光照计算,但是大多数人趋向于更偏向在观察空间进行光照计算。在观察空间计算的优势是,观察者的位置总是在(0, 0, 0),所以你已经零成本地拿到了观察者的位置。然而,若以学习为目的,我认为在世界空间中计算光照更符合直觉。如果你仍然希望在观察空间计算光照的话,你需要将所有相关的向量也用观察矩阵进行变换(不要忘记也修改法线矩阵)。

要得到观察者的世界空间坐标,我们直接使用摄像机的位置向量即可。

首先,我们定义一个镜面强度(Specular Intensity)变量,给镜面高光一个中等亮度颜色,让它不要产生过度的影响。如果我们把它设置为1.0f,我们会得到一个非常亮的镜面光分量,这对于一个珊瑚色的立方体来说有点太多了。

下一步,我们计算视线方向向量,和对应的沿着法线轴的反射向量:

vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);

需要注意的是我们对lightDir向量进行了取反。reflect函数要求第一个向量是光源指向片段位置的向量,但是lightDir当前正好相反,是从片段指向光源(由先前我们计算lightDir向量时,减法的顺序决定)。为了保证我们得到正确的reflect向量,我们通过对lightDir向量取反来获得相反的方向。第二个参数要求是一个法向量,所以我们提供的是已标准化的norm向量。

计算镜面分量:

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

我们先计算视线方向与反射方向的点乘(并确保它不是负值),然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。在下面的图片里,你会看到不同反光度的视觉效果影响:

在这里插入图片描述

在光照着色器的早期,开发者曾经在顶点着色器中实现冯氏光照模型。在顶点着色器中做光照的优势是,相比片段来说,顶点要少得多,因此会更高效,所以(开销大的)光照计算频率会更低。然而,顶点着色器中的最终颜色值是仅仅只是那个顶点的颜色值,片段的颜色值是由插值光照颜色所得来的。结果就是这种光照看起来不会非常真实,除非使用了大量顶点。

在这里插入图片描述

在顶点着色器中实现的冯氏光照模型叫做Gouraud着色(Gouraud Shading),而不是冯氏着色(Phong Shading)。记住,由于插值,这种光照看起来有点逊色。冯氏着色能产生更平滑的光照效果。

在这里插入图片描述

完整代码

package light

// 光照

import (
	"runtime"

	"github.com/go-gl/gl/v4.1-core/gl"
	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/go-gl/mathgl/mgl32"
	"github.com/phprao/go-graphic/util"
)

const (
	width  = 800
	height = 600

	vertexShaderSource = `
	#version 410

	in vec3 aPos;
	in vec3 aNormal;

	out vec3 FragPos;
	out vec3 Normal;

	uniform mat4 model;
	uniform mat4 view;
	uniform mat4 projection;
	uniform mat3 normalModel;

	void main() {
		Normal = normalModel * aNormal;
		FragPos = vec3(model * vec4(aPos, 1.0));
		gl_Position = projection * view * model * vec4(aPos, 1.0);
	}
	` + "\x00"

	fragmentShaderSource = `
	#version 410
    
	out vec4 frag_colour;

	in vec3 Normal;
	in vec3 FragPos;

	uniform vec3 lightPos;
	uniform vec3 viewPos;
	uniform vec3 lightColor;
	uniform vec3 objectColor;

	void main() {
		// 环境光
		float ambientStrength = 0.1;
		vec3 ambient = ambientStrength * lightColor;

		// 漫反射光
		vec3 norm = normalize(Normal);
		vec3 lightDir = normalize(lightPos - FragPos);
		float diff = max(dot(norm, lightDir), 0.0);
		vec3 diffuse = diff * lightColor;

		// 镜面光
		float specularStrength = 0.5;
		vec3 viewDir = normalize(viewPos - FragPos);
		vec3 reflectDir = reflect(-lightDir, norm);
		float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
		vec3 specular = specularStrength * spec * lightColor;

		frag_colour = vec4((ambient + diffuse + specular) * objectColor, 1.0);
	}
	` + "\x00"

	vertexShaderSource2 = `
	#version 410

	in vec3 aPos;

	uniform mat4 model;
	uniform mat4 view;
	uniform mat4 projection;

	void main() {
		gl_Position = projection * view * model * vec4(aPos, 1.0);
	}
	` + "\x00"

	fragmentShaderSource2 = `
	#version 410
    
	out vec4 frag_colour;

	void main() {
		frag_colour = vec4(1.0);
	}
	` + "\x00"
)

var (
	vertices = []float32{
		-0.5, -0.5, -0.5, 0.0, 0.0, -1.0,
		0.5, -0.5, -0.5, 0.0, 0.0, -1.0,
		0.5, 0.5, -0.5, 0.0, 0.0, -1.0,
		0.5, 0.5, -0.5, 0.0, 0.0, -1.0,
		-0.5, 0.5, -0.5, 0.0, 0.0, -1.0,
		-0.5, -0.5, -0.5, 0.0, 0.0, -1.0,

		-0.5, -0.5, 0.5, 0.0, 0.0, 1.0,
		0.5, -0.5, 0.5, 0.0, 0.0, 1.0,
		0.5, 0.5, 0.5, 0.0, 0.0, 1.0,
		0.5, 0.5, 0.5, 0.0, 0.0, 1.0,
		-0.5, 0.5, 0.5, 0.0, 0.0, 1.0,
		-0.5, -0.5, 0.5, 0.0, 0.0, 1.0,

		-0.5, 0.5, 0.5, -1.0, 0.0, 0.0,
		-0.5, 0.5, -0.5, -1.0, 0.0, 0.0,
		-0.5, -0.5, -0.5, -1.0, 0.0, 0.0,
		-0.5, -0.5, -0.5, -1.0, 0.0, 0.0,
		-0.5, -0.5, 0.5, -1.0, 0.0, 0.0,
		-0.5, 0.5, 0.5, -1.0, 0.0, 0.0,

		0.5, 0.5, 0.5, 1.0, 0.0, 0.0,
		0.5, 0.5, -0.5, 1.0, 0.0, 0.0,
		0.5, -0.5, -0.5, 1.0, 0.0, 0.0,
		0.5, -0.5, -0.5, 1.0, 0.0, 0.0,
		0.5, -0.5, 0.5, 1.0, 0.0, 0.0,
		0.5, 0.5, 0.5, 1.0, 0.0, 0.0,

		-0.5, -0.5, -0.5, 0.0, -1.0, 0.0,
		0.5, -0.5, -0.5, 0.0, -1.0, 0.0,
		0.5, -0.5, 0.5, 0.0, -1.0, 0.0,
		0.5, -0.5, 0.5, 0.0, -1.0, 0.0,
		-0.5, -0.5, 0.5, 0.0, -1.0, 0.0,
		-0.5, -0.5, -0.5, 0.0, -1.0, 0.0,

		-0.5, 0.5, -0.5, 0.0, 1.0, 0.0,
		0.5, 0.5, -0.5, 0.0, 1.0, 0.0,
		0.5, 0.5, 0.5, 0.0, 1.0, 0.0,
		0.5, 0.5, 0.5, 0.0, 1.0, 0.0,
		-0.5, 0.5, 0.5, 0.0, 1.0, 0.0,
		-0.5, 0.5, -0.5, 0.0, 1.0, 0.0,
	}
)

func Run() {
	runtime.LockOSThread()
	window := util.InitGlfw(width, height, "light")
	defer glfw.Terminate()

	pointNum := int32(len(vertices)) / 5

	program1, _ := util.InitOpenGL(vertexShaderSource, fragmentShaderSource)
	vao1 := util.MakeVaoWithAttrib(program1, vertices, nil, []util.VertAttrib{{Name: "aPos", Size: 3}, {Name: "aNormal", Size: 3}})
	program2, _ := util.MakeProgram(vertexShaderSource2, fragmentShaderSource2)
	vao2 := util.MakeVaoWithAttrib(program2, vertices, nil, []util.VertAttrib{{Name: "aPos", Size: 3}, {Name: "aNormal", Size: 3}})

	gl.ClearColor(0.1, 0.1, 0.1, 1.0)
	gl.Enable(gl.DEPTH_TEST)

	lightPos := mgl32.Vec3{2, 0, 0}
	lightColor := mgl32.Vec3{1, 1, 1}
	objectColor := mgl32.Vec3{1, 0.5, 0.31}

	cameraPos := mgl32.Vec3{0, 0, 3}
	cameraFront := mgl32.Vec3{0, 0, -1}
	cameraUp := mgl32.Vec3{0, 1, 0}

	camera := util.NewCamera(cameraPos, cameraFront, cameraUp, width, height)
	camera.SetCursorPosCallback(window)

	for !window.ShouldClose() {
		gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
        
         // lightPos = mgl32.Vec3{2, float32(math.Sin(glfw.GetTime())), 0}

		// 画箱子
		gl.UseProgram(program1)

		gl.Uniform3fv(gl.GetUniformLocation(program1, gl.Str("lightColor\x00")), 1, &lightColor[0])
		gl.Uniform3fv(gl.GetUniformLocation(program1, gl.Str("lightPos\x00")), 1, &lightPos[0])
		gl.Uniform3fv(gl.GetUniformLocation(program1, gl.Str("objectColor\x00")), 1, &objectColor[0])
		gl.Uniform3fv(gl.GetUniformLocation(program1, gl.Str("viewPos\x00")), 1, &camera.CameraPos[0])
		view := camera.LookAt()
		projection := camera.Perspective()

		model1 := mgl32.Ident4()
		normalModel := model1.Inv().Transpose().Mat3()
		gl.UniformMatrix4fv(gl.GetUniformLocation(program1, gl.Str("model\x00")), 1, false, &model1[0])
		gl.UniformMatrix4fv(gl.GetUniformLocation(program1, gl.Str("view\x00")), 1, false, &view[0])
		gl.UniformMatrix4fv(gl.GetUniformLocation(program1, gl.Str("projection\x00")), 1, false, &projection[0])
		gl.UniformMatrix3fv(gl.GetUniformLocation(program1, gl.Str("normalModel\x00")), 1, false, &normalModel[0])
		gl.BindVertexArray(vao1)
		gl.DrawArrays(gl.TRIANGLES, 0, pointNum)

		// 画光源
		gl.UseProgram(program2)
		model2 := mgl32.Translate3D(lightPos.X(), lightPos.Y(), lightPos.Z()).Mul4(mgl32.Scale3D(0.2, 0.2, 0.2))
		gl.UniformMatrix4fv(gl.GetUniformLocation(program2, gl.Str("model\x00")), 1, false, &model2[0])
		gl.UniformMatrix4fv(gl.GetUniformLocation(program2, gl.Str("view\x00")), 1, false, &view[0])
		gl.UniformMatrix4fv(gl.GetUniformLocation(program2, gl.Str("projection\x00")), 1, false, &projection[0])
		gl.BindVertexArray(vao2)
		gl.DrawArrays(gl.TRIANGLES, 0, pointNum)

		glfw.PollEvents()
		window.SwapBuffers()
	}
}

我们也可以使光源动态移动起来,观察一下,有利于理解冯氏光照模型。

总结一下:环境光好理解;漫反射光是由物体和光源的相对位置决定的;镜面光是由物体,光源,相机三者的相对位置决定的。

材质(Material)

当描述一个表面时,我们可以分别为三个光照分量定义一个材质颜色(Material Color):环境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)和镜面光照(Specular Lighting)。通过为每个分量指定一个颜色,我们就能够对表面的颜色输出有细粒度的控制了。现在,我们再添加一个反光度(Shininess)分量,结合上述的三个颜色,我们就有了全部所需的材质属性了:

struct Material {
    vec3 ambient; // 环境光颜色
    vec3 diffuse; // 漫反射光颜色
    vec3 specular; // 镜面光颜色
    float shininess; // 镜面光的反光度
}; 

uniform Material material;

我们为冯氏光照模型的每个分量都定义一个颜色向量。ambient材质向量定义了在环境光照下这个表面反射的是什么颜色,通常与表面的颜色相同。diffuse材质向量定义了在漫反射光照下表面的颜色。漫反射颜色(和环境光照一样)也被设置为我们期望的物体颜色。specular材质向量设置的是表面上镜面高光的颜色(或者甚至可能反映一个特定表面的颜色)。最后,shininess影响镜面高光的散射/半径。

devernay.free.fr 中的一个表格展示了一系列材质属性,它们模拟了现实世界中的真实材质。

搞清楚一个物体正确的材质设定是个困难的工程,这主要需要实验和丰富的经验。用了不合适的材质而毁了物体的视觉质量是件经常发生的事。

有了材质颜色之后,我们就不需要再传入物体的颜色了。

#version 410

out vec4 frag_colour;

in vec3 Normal;
in vec3 FragPos;

uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;

struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
}; 

uniform Material material;

void main() {
    // 环境光
    vec3 ambient = lightColor * material.ambient;

    // 漫反射光
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = lightColor * diff * material.diffuse;

    // 镜面光
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = lightColor * spec * material.specular;

    frag_colour = vec4(ambient + diffuse + specular, 1.0);
}

传值

gl.Uniform3f(gl.GetUniformLocation(program1, gl.Str("material.ambient\x00")), 1, 0.5, 0.31)
gl.Uniform3f(gl.GetUniformLocation(program1, gl.Str("material.diffuse\x00")), 1, 0.5, 0.31)
gl.Uniform3f(gl.GetUniformLocation(program1, gl.Str("material.specular\x00")), 0.5, 0.5, 0.5)
gl.Uniform1f(gl.GetUniformLocation(program1, gl.Str("material.shininess\x00")), 32)

在这里插入图片描述

光的属性

上面的物体实在太亮了,我们还需要为环境光,漫反射光,镜面光分别设置颜色。

#version 410
    
out vec4 frag_colour;

in vec3 Normal;
in vec3 FragPos;

uniform vec3 viewPos;

struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
}; 

uniform Material material;

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;

void main() {
    // 环境光
    vec3 ambient = light.ambient * material.ambient;

    // 漫反射光
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * material.diffuse;

    // 镜面光
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * material.specular;

    frag_colour = vec4(ambient + diffuse + specular, 1.0);
}
gl.Uniform3f(gl.GetUniformLocation(program1, gl.Str("light.ambient\x00")), 0.2, 0.2, 0.2)
gl.Uniform3f(gl.GetUniformLocation(program1, gl.Str("light.diffuse\x00")), 0.5, 0.5, 0.5)
gl.Uniform3f(gl.GetUniformLocation(program1, gl.Str("light.specular\x00")), 1, 1, 1)
gl.Uniform3fv(gl.GetUniformLocation(program1, gl.Str("light.position\x00")), 1, &lightPos[0])

在这里插入图片描述

不同的光源颜色

上面我们已经将光的各个分量拆分了出来,我们可以通过改变这些分量来改变光的颜色,已达到不同的效果。

光照贴图

就是纹理贴图加光照的效果。

漫反射贴图

此处将漫反射贴图(Diffuse Map)来替换掉漫反射光。它是一个表现了物体所有的漫反射颜色的纹理图像。

在着色器中使用漫反射贴图的方法和纹理教程中是完全一样的。但这次我们会将纹理储存为Material结构体中的一个sampler2D。我们将之前定义的vec3漫反射颜色向量替换为漫反射贴图。

我们也移除了环境光材质颜色向量,因为环境光颜色在几乎所有情况下都等于漫反射颜色,所以我们不需要将它们分开储存,不要忘记将环境光的材质颜色设置为漫反射材质颜色同样的值。

在这里插入图片描述

镜面光贴图

就是将镜面光也添加上贴图。

我们认为箱子中间的木头是不会反射光线的,因此在第二张贴图上将木头部分置为黑色。

以下是添加了漫反射贴图和镜面贴图的效果。

在这里插入图片描述

光源

1、平行光/定向光

当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。当我们使用一个假设光源处于无限远处的模型时,它就被称为定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的。定向光非常好的一个例子就是太阳。

在这里插入图片描述

因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致,场景中每个物体的光照计算将会是类似的。

我们可以定义一个光线方向向量而不是位置向量来模拟一个定向光。着色器的计算基本保持不变,但这次我们将直接使用光的direction向量而不是通过position来计算lightDir向量。

#version 410

......

struct Light {
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;

void main() {
    ......
    vec3 lightDir = normalize(-light.direction);
    ......
    frag_colour = vec4(ambient + diffuse + specular, 1.0);
}
// 取反之后为照射方向,也就是说此时是从 Z=1 照射到 z=0
lightDirection := mgl32.Vec3{0, 0, -1}

我们让照射光的方向动态变化

lightDirection = mgl32.Vec3{0, float32(math.Sin(glfw.GetTime())), -1}

在这里插入图片描述

2、点光源

定向光对于照亮整个场景的全局光源是非常棒的,但除了定向光之外我们也需要一些分散在场景中的点光源(Point Light)。点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。想象作为投光物的灯泡和火把,它们都是点光源。

在这里插入图片描述

在之前的教程中,我们一直都在使用一个(简化的)点光源。我们在给定位置有一个光源,它会从它的光源位置开始朝着所有方向散射光线。然而,我们定义的光源模拟的是永远不会衰减的光线,这看起来像是光源亮度非常的强。在大部分的3D模拟中,我们都希望模拟的光源仅照亮光源附近的区域而不是整个场景。

如果你将10个箱子加入到上一节光照场景中,你会注意到在最后面的箱子和在灯面前的箱子都以相同的强度被照亮,并没有定义一个公式来将光随距离衰减。我们希望在后排的箱子与前排的箱子相比仅仅是被轻微地照亮。

衰减

随着光线传播距离的增长逐渐削减光的强度通常叫做衰减(Attenuation)。随距离减少光强度的一种方式是使用一个线性方程。这样的方程能够随着距离的增长线性地减少光的强度,从而让远处的物体更暗。然而,这样的线性方程通常会看起来比较假。在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了。所以,我们需要一个不同的公式来减少光的强度。

在这里插入图片描述

在这里插入图片描述

d表示距离,其他参数都是系数设置。

在这里插入图片描述

3、聚光

我们要讨论的最后一种类型的光是聚光(Spotlight)。聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/695971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言初阶】带你轻松玩转所有常用操作符最终篇——下标引用、函数调用和结构体成员,表达式求值

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,这里是君兮_,前几天非常忙没空更新,现在终于闲下来了,这是今天爆肝更新的第二篇,也是我们【C语言初阶】带你轻松玩转所有常用操作符的最后一篇,废话不多说…

nestjs post请求提交数据量太大会报错 [ExceptionsHandler] request entity too large

背景 在传给后端接口的数据,是一个超过1.5mb的json字符串,通过nest filter的时候直接被拦截掉了,返回【 请求体太长】的错误。 还没有到达contraller,就直接被框架拦截掉了, import { ExceptionFilter, Catch, Argu…

vue——实现表格的拖拽排序功能——技能提升

最近在写后台管理系统时,遇到一个需求,就是要实现表格的排序。 就是拖动某一行数据,放在指定位置,然后保存的时候根据最终的排序来处理生产单顺序字段。 参考大神的链接:vue-实现拖拽排序功能:https://ju…

mac苹果电脑,怎么批量修改文件名称

mac苹果电脑,如何批量修改文件名称?在苹果电脑上对文件名称进行修改是一件非常简单的操作,相信任何mac电脑用户都知道怎么操作,只需要选中要修改名称的文件,然后点击鼠标右键,然后会弹出一个菜单&#xff0…

layui代码记录

// 归档弹框jsguiDangRemarkInfo : function(activiti, buttonConf, call) {layui.use([layer, form, laydate], function () {var layer layui.layer;var form layui.form;var laydate layui.laydate;layer.open({type: 1,title: 归档备注信息,area: [350px, 300px],conten…

Redis 6.2.4集群搭建

1. 说明 这里使用的是redis的cluster集权模式,没有用哨兵模式(哨兵模式依赖哨兵节点,哨兵节点一旦挂掉就不再高可用了,因此没有采用)。 由于Redis Cluster至少需要6个节点,因此,这里咱们采用的…

【运维】Linux系统 AnolisOS [CentOs替代品]

【运维】AnolisOS 安装 CentOs替代品 下载 - OpenAnolis 龙蜥操作系统开源社区 Index of /anolis/ (aliyun.com)

(编辑器)HBuilderX怎么自动格式化代码

(编辑器)HBuilderX怎么自动格式化代码 解决参考:https://blog.csdn.net/G806218/article/details/127833356

CSDN 个性化推荐的数据治理

目录 1. 背景2. 数据治理2.1 从内容层面提升数据质量2.2 从用户层面提升数据质量2.3 增加特定数据的曝光2.4 保证数据的正确性与实效性 3. 总结 相关阅读 CSDN 个性化推荐系统的设计和演进如何支持研发对CSDN个性化推荐系统重构 1. 背景 CSDN 的个性化推荐流在主站中是一个很…

用UDP套接字实现客户端和服务端通信

IP地址和port端口号 IP地址 数据有IP(公网)标识一台唯一的主机。 port端口号 为了更好的标识一台主机上服务进程的唯一性,我们采用端口号port,标识服务器进程,客户端进程的唯一性! ip端口号 IP地址(主机全网唯一性) 该主机上的端…

一年省七位数,得物自建HFDS在 Flink Checkpoint 场景下的应用实践

1 背景 随着Flink实例的迁移下云以及新增需求接入,自建Flink平台规模逐渐壮大,当前总计已超4万核运行在自建的K8S集群中,然而 Flink 任务数的增加,特别是大状态任务,每次Checkpoint 时会产生脉冲式带宽占用&#xff0…

统计项目代码行数工具cloc

Ubuntu用户 使用cloc在ubuntu内统计代码行数 安装cloc工具 sudo apt-get install cloc进入需要统计的目录内,然后执行 cloc .然后就会显示文件目录中的文件数(files)、空白行数(blank)、注释行数(comment)和代码行数(code)。 Windows 用户 也是使用cloc工具 …

leecode-下一排列

题目 题目 分析 妈呀,其实我直接调用函数,一行代码就通过了hhh,不过这种取巧的方式不可取,还是得老老实实的写。 首先需要明白什么叫下一排列? 比如输入: 1 5 8 4 7 6 5 3 1 答案就是: 1 5 …

什么是楼宇卫生间智慧厕所系统

楼宇卫生间智慧厕所系统是专为写字楼、办公楼、商场、集团大厦、工厂等应用场景所设计的解决方案。它利用全自动采集和监控智能化、无线数据传输功能,通过云平台管理软件和手机端应用,实现了公厕的智能化管理和使用者的便利。 随着城市化进程的加速&…

[Json]控制返回数据是否包含某个属性

控制返回数据是否包含某个属性 在我们返回给前端的Json格式的数据时,通常我们会定义一个类,里面定义几个成员变量用来定义返回给前端的具体内容,例如: package cn.tedu.csmall.commons.web;import io.swagger.annotations.ApiMo…

HDLbits--Exams/2013 q2bfsm

try1: module top_module (input clk,input resetn, // active-low synchronous resetinput x,input y,output f,output g ); parameter a0,b1,x12,x23,y14,y25,g16,g07;//b为resetn无效后的状态,在b状态使f保持一个周期 //b收到1后转移到x1,x1收到…

基于双机多线程的程序加速设计

摘要 不断提高程序的运行效率,而又不影响程序功能是程序员的不竭追求。本项目旨在利用并行技术进一步提高程序的效率。 程序设计中,主要实现了百万级数据的求和、求最大值以及排序功能。其中,排序功能使用快速排序算法和归并算法实现。共采用…

lenovo联想笔记本ThinkBook 14 Gen5+ IRH(21HW)原装Win11系统镜像原厂OEM恢复出厂状态

LENOVO联想笔记本电脑,ThinkBook 14 Gen5 IRH(21HW),原厂Windows11原装OEM系统,恢复出厂时状态系统 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具:16G或以上的U盘 文件格式&#x…

使用凌鲨查看mysql数据

MySQL是一种开源的关系型数据库管理系统,它被广泛应用于软件开发领域。它具有高可靠性、高性能、易于使用和可扩展性等优点,被许多大型企业和网站所采用。MySQL支持多种编程语言和操作系统,可以轻松地与其他应用程序集成。 继之前我们在凌鲨…

python如何将图片显示在网页上

from flask import Flask, render_template_string import base64 import cv2import osapp Flask(__name__)# 读取图像app.route(/)def index():# 读取图像文件并将其转换为Base64编码的字符串img_path 1.pngimg_data open(img_path, rb).read()img_base64 base64.b64encod…