C-3:绘制一个彩色的四棱锥并添加光照效果

C-3:绘制一个彩色的四棱锥并添加光照效果


作业要求:
a. 四棱锥边长均为 2;
b. 四棱锥各个顶点颜色不同;
c. 四棱锥的中心为(1,2,3);
d. 要求使用多种光,包括环境光、镜面光和散射光等,可以通过控制物体对光的反射
材质因子和不同光的因子达到不同的光照效果。

运行说明参见README.md文件。效果展示见录屏视频。

初始化OpenGL

建立窗口,注册回调函数,加载glad,并设置OpenGL的基本属性。

	glfwInit();

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); // comment this line in a release build!

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Materials", NULL, NULL);
    if (window == NULL){
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);// 用GLFW注册了回调函数之后,鼠标一移动mouse_callback函数就会被调用
    glfwSetScrollCallback(window, scroll_callback);//注册鼠标滚轮的回调函数

    if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // configure global OpenGL state
    glEnable(GL_DEPTH_TEST);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

准备数据

建立顶点数组对象。同时分别注册顶点的位置属性、颜色属性和法向量属性,属性的序号值指明了数据的存放位置,以在着色器中通过对应位置找到位置和颜色数据。
顶点的position属性满足题设要求。Color属性自己随便设。Normal属性需要自己算,注意的是这里我采用了sharp edge的形式,在渲染每一个三角形面片时,三个顶点都用相同的法向量(而非像常见的,用一个顶点周围所有面的法向量来平均得到顶点的法向量,这样得到的光照效果会光滑过渡)。因此在后面渲染时也是使用glDrawArrays(GL_TRIANGLES, 0, 36)来绘制每个三角形,即不同的三角形面片之间不能复用顶点数据。

	float sqr2 = sqrt(2.0f);
    float pyramid_vertices[] = {
        // Positions        Color              Normal
         0.0f, sqr2, 0.0f,  1.0f, 0.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //0
         0.0f, 0.0f, sqr2,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f, 0.0f,    //2
         sqr2, 0.0f, 0.0f,  0.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //1
        
         0.0f, sqr2, 0.0f,  1.0f, 0.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //0
        -sqr2, 0.0f, 0.0f,  1.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //3
         0.0f, 0.0f, sqr2,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f, 0.0f,    //2
         
         0.0f, sqr2, 0.0f,  1.0f, 0.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //0
         0.0f, 0.0f,-sqr2,  1.0f, 0.0f, 1.0f,  0.0f, 0.0f, 0.0f,    //4
        -sqr2, 0.0f, 0.0f,  1.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //3

         0.0f, sqr2, 0.0f,  1.0f, 0.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //0
         sqr2, 0.0f, 0.0f,  0.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //1
         0.0f, 0.0f,-sqr2,  1.0f, 0.0f, 1.0f,  0.0f, 0.0f, 0.0f,    //4

         sqr2, 0.0f, 0.0f,  0.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //1
         0.0f, 0.0f, sqr2,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f, 0.0f,    //2
        -sqr2, 0.0f, 0.0f,  1.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //3

         sqr2, 0.0f, 0.0f,  0.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //1
        -sqr2, 0.0f, 0.0f,  1.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f,    //3
         0.0f, 0.0f,-sqr2,  1.0f, 0.0f, 1.0f,  0.0f, 0.0f, 0.0f,    //4
    };
    for (int iFace = 0; iFace < 6; iFace++) {
        int vid0 = 3*iFace, vid1 = vid0+1, vid2 = vid1+1;
        glm::vec3 a = glm::vec3(pyramid_vertices[vid1*9+0]-pyramid_vertices[vid0*9+0], \
                                 pyramid_vertices[vid1*9+1]-pyramid_vertices[vid0*9+1], \
                                 pyramid_vertices[vid1*9+2]-pyramid_vertices[vid0*9+2]);
        glm::vec3 b = glm::vec3(pyramid_vertices[vid2*9+0]-pyramid_vertices[vid0*9+0], \
                                 pyramid_vertices[vid2*9+1]-pyramid_vertices[vid0*9+1], \
                                 pyramid_vertices[vid2*9+2]-pyramid_vertices[vid0*9+2]);
        glm::vec3 nm= glm::normalize(glm::cross(a, b));
        for (int i = vid0; i <= vid2; i++) {
            pyramid_vertices[i*9+6] = nm[0];
            pyramid_vertices[i*9+7] = nm[1];
            pyramid_vertices[i*9+8] = nm[2];
        }
    }
    // first, configure the pyramid's VAO, VBO, EBO
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(pyramid_vertices), pyramid_vertices, GL_STATIC_DRAW);
    
    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(3*sizeof(float)));
    glEnableVertexAttribArray(1);
    // Normal attribute
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(6*sizeof(float)));
    glEnableVertexAttribArray(2);

    glBindVertexArray(0);//unbind

同时需要注意的是,在定义pyramid_vertices数组时,需要合理安排顶点顺序,保证每个三角形面按此顶点定义顺序(在右手坐标系下)的面法向量朝外。

除了四棱锥,还设置另外一个充当光源的物体(其实不必要,只是为了好看而已)。也类似地炮制。

	float cube_vertices[] = {
        // Positions
        -0.5f, -0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f,  0.5f, -0.5f,
        0.5f,  0.5f, -0.5f,
        -0.5f,  0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,

        -0.5f, -0.5f,  0.5f,
        0.5f, -0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f, -0.5f,  0.5f,

        -0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,

        0.5f,  0.5f,  0.5f,
        0.5f,  0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f, -0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,

        -0.5f, -0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f, -0.5f,  0.5f,
        0.5f, -0.5f,  0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f, -0.5f, -0.5f,

        -0.5f,  0.5f, -0.5f,
        0.5f,  0.5f, -0.5f,
        0.5f,  0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f, -0.5f
    };

    // second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube)
    unsigned int lightCubeVAO, lightCubeVBO;
    glGenVertexArrays(1, &lightCubeVAO);
    glGenBuffers(1, &lightCubeVBO);

    glBindVertexArray(lightCubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, lightCubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cube_vertices), cube_vertices, GL_STATIC_DRAW);
    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(0);//unbind

建立着色器

Shader pyramidShader ("../src/materials.vs", "../src/materials.fs");
Shader lightCubeShader("../src/lightcube.vs", "../src/lightcube.fs");

对于片段着色器,需要注意将法向量从局部空间转换到世界空间。而由于是在世界空间中进行所有光照的计算,因此我们需要一个在世界空间中的顶点位置worldCoord

#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec3 aNormal;

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

out vec3 objectColor;
out vec3 Normal;
out vec3 worldCoord;

void main()
{
    worldCoord = vec3(model * vec4(aPos, 1.0));
    
    //Normal = aNormal;
    //法线矩阵:模型矩阵左上角的逆矩阵的转置矩阵,用它来移除对法向量错误缩放的影响
    //Normal = mat3(transpose(inverse(model))) * aNormal;
    Normal = transpose(inverse(mat3(model))) * aNormal;

    gl_Position = projection * view * model * vec4(aPos, 1.0);
    objectColor = aColor;
}

涉及环境光照、漫反射光照、镜面反射光照的片段着色器,较之前复杂一些。一般地,需要定义物体的材质struct Material,其中的ambientdiffusespecular三个分量均为vec3类型,以能表征各向异性的材料。对光源struct Light也是同理。
在这里需要根据顶点着色器传入的顶点的世界空间中的坐标和法向量计算与光线之间的漫反射和镜面反射分量。所有分量叠加后再与物体本身的颜色objectColor相乘。

#version 460 core
out vec4 FragColor;
  
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;    
    float shininess;
}; 

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform vec3 viewPos;//观察者/摄像机的位置
uniform Material material;
uniform Light light;

in vec3 objectColor;
in vec3 Normal;
in vec3 worldCoord;

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

    //漫反射光照
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(light.position - worldCoord);//从片段指向光源
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * material.diffuse);

    //镜面光照
    vec3 specular;
    if (diff > 0.0) {//要使光照方向与法向夹角小于90°才能有镜面光照效果,否则白搭
        vec3 viewDir = normalize(viewPos - worldCoord);//从片段指向摄像机
        vec3 reflectDir = reflect(-lightDir, norm);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//高光的反光度(Shininess),一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。
        specular = spec * light.specular * material.specular; 
    } else {
        specular = vec3(0.0f, 0.0f, 0.0f);
    }

    vec3 result = (ambient + diffuse + specular) * objectColor;
    // vec3 result = Normal;
    FragColor = vec4(result, 1.0);
}

物体材质

关于物体材质的选择上,我参考了LearnOpenGL网站上材质一节中提供的链接中包含的材质列表。将所有可选的材质添加到程序中作为materials_lists。但值得注意的是,如这张材质列表提供时所注明的:

注意材质表格中的环境光值可能与漫反射值不一样,它们没有考虑光照的强度。要想纠正这一问题,你需要将所有的光照强度都设置为vec3(1.0),这样才能得到正确的输出

因此若使用这张材质列表的所有数据时,在渲染循环中需要设置光源参数为

	glm::vec3 curr_lightPos = glm::vec3(radius*cos(currentFrame), lightPos.y, radius*sin(currentFrame));
	pyramidShader.setVec3("light.position", curr_lightPos);
	pyramidShader.setVec3("light.ambient", glm::vec3(1.0f, 1.0f, 1.0f));
	pyramidShader.setVec3("light.diffuse", glm::vec3(1.0f, 1.0f, 1.0f));
	pyramidShader.setVec3("light.specular", glm::vec3(1.0f, 1.0f, 1.0f)); 

为了增加趣味性,我将光源位置设置成在某个高度(y坐标固定)上围绕四棱锥中心轴不断旋转。

一共有24种材质供选择,切换材质通过键盘输入0~9, q, e, r, t, y, u, i, o, p, f, g, h, j ,k等键。在process_input函数里切换:

	if      (glfwGetKey(window, GLFW_KEY_0) == GLFW_PRESS)
        material_id = 0;
    else if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS)
        material_id = 1;
    ... ...    

同时在渲染循环中根据material_id切换:

	// material's effect
    pyramidShader.setVec3("material.ambient", materials_lists[material_id][0], materials_lists[material_id][1], materials_lists[material_id][2]);
    pyramidShader.setVec3("material.diffuse", materials_lists[material_id][3], materials_lists[material_id][4], materials_lists[material_id][5]);
    pyramidShader.setVec3("material.specular",materials_lists[material_id][6], materials_lists[material_id][7], materials_lists[material_id][8]);
    pyramidShader.setFloat("material.shininess", materials_lists[material_id][9]);

渲染四棱锥和光源物体

老生常谈地,设置着色器中的世界空间矩阵model,视角矩阵view,和投影矩阵projection。每次draw之前先将顶点数组对象绑定。

	// view/projection transformations
	glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    glm::mat4 view = camera.GetViewMatrix();
    pyramidShader.setMat4("projection", projection);
    pyramidShader.setMat4("view", view);
    // world transformation
    glm::mat4 model = glm::mat4(1.0f);
    // model = glm::translate(model, glm::vec3(1.0f, 2.0f, 3.0f));
    pyramidShader.setMat4("model", model);

    // render the pyramid
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 18);

    /* -------------------------------------------------------------------------------------------- */

    // also draw the lamp object (光源)
    lightCubeShader.use();
    lightCubeShader.setMat4("projection", projection);
    lightCubeShader.setMat4("view", view);
    model = glm::mat4(1.0f);
    model = glm::translate(model, curr_lightPos);
    model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
    lightCubeShader.setMat4("model", model);

    glBindVertexArray(lightCubeVAO);
    glDrawArrays(GL_TRIANGLES, 0, 36);

效果

选取光源移动到不同位置时的效果,展示如下。

图1
图2
图3
下面是另一种材质的效果。
图1
图2
图3
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要实现这个效果,您可以按照以下步骤进行操作: 1. 创建一个棱锥的模型,并将其导入到场景中。 2. 为每个面创建一个不同的材质,并将其分配给棱锥的各个面。 3. 对于每个面,使用顶点着色的方法将颜色渐变效果应用到每个顶点。这可以通过在每个顶点处指定不同的颜色值来实现。 4. 保存您的场景并查看效果。 下面是一个示例代码片段,可以帮助您实现此效果: ```javascript // 创建棱锥的模型 var geometry = new THREE.ConeGeometry( 5, 10, 4 ); // 创建个不同的材质 var material1 = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); var material2 = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); var material3 = new THREE.MeshBasicMaterial( { color: 0x0000ff } ); var material4 = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); // 将材质分配给棱锥的各个面 var materials = [ material1, material2, material3, material4 ]; var mesh = new THREE.Mesh( geometry, materials ); // 对于每个面,使用顶点着色的方法将颜色渐变效果应用到每个顶点 for ( var i = 0; i < mesh.geometry.vertices.length; i ++ ) { var vertex = mesh.geometry.vertices[ i ]; var color = new THREE.Color( 0xffffff ); color.setRGB( vertex.x / 10 + 0.5, vertex.y / 10 + 0.5, vertex.z / 10 + 0.5 ); mesh.geometry.colors[ i ] = color; } // 启用顶点颜色 mesh.geometry.colorsNeedUpdate = true; // 将棱锥添加到场景中 scene.add( mesh ); ``` 请注意,此示例代码仅用于说明目的,您可能需要进行适当的修改才能使其适用于您的场景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zongy17

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值