如何在GLSL片元着色器中获取相对于法线的视角角度?
在openFrameworks中实现基于视角角度的片元着色器
刚好我之前在openFrameworks里折腾过类似的视角色变效果,给你梳理一下核心思路和可直接运行的代码,不管是矩形还是球体都能生效~
核心逻辑其实很简单:我们需要计算表面法线与相机视线方向的夹角——当相机完全正对表面(夹角为0°,法线和视线平行)时,点积结果接近1,显示红色;当视角转到侧面(夹角90°,法线和视线垂直)时,点积结果接近0,显示蓝色;中间角度就用这个点积值来平滑混合两种颜色。
1. openFrameworks端的基础设置
首先在你的ofApp.cpp里,需要加载着色器、创建测试几何体,并把相机位置等关键参数传递给着色器:
#include "ofApp.h" void ofApp::setup(){ ofEnableDepthTest(); // 开启深度测试,避免几何体遮挡问题 shader.load("shaders/vertShader.glsl", "shaders/fragShader.glsl"); // 可以切换测试立方体(矩形的3D版本)或球体 // rect.set(200, 200, 200); sphere.setRadius(200); sphere.setResolution(64); // 提高球体法线精度,让颜色过渡更平滑 } void ofApp::update(){ } void ofApp::draw(){ ofBackground(20); ofPushMatrix(); ofTranslate(ofGetWidth()/2, ofGetHeight()/2); // 让几何体旋转,方便观察不同视角的颜色变化 ofRotateY(ofGetElapsedTimef() * 30); ofRotateX(ofGetElapsedTimef() * 20); shader.begin(); // 传递相机位置给着色器(openFrameworks默认相机在(0,0,1000)) shader.setUniform3f("cameraPos", ofVec3f(0, 0, 1000)); // 传递模型矩阵,用于计算世界空间的法线和顶点位置 shader.setUniformMatrix4f("modelMatrix", ofGetCurrentModelMatrix()); shader.setUniformMatrix4f("modelViewMatrix", ofGetCurrentModelViewMatrix()); shader.setUniformMatrix4f("projectionMatrix", ofGetCurrentProjectionMatrix()); // 绘制几何体 // rect.draw(); sphere.draw(); shader.end(); ofPopMatrix(); }
2. 顶点着色器(vertShader.glsl)
顶点着色器需要把世界空间下的法线和世界空间下的顶点位置传递给片元着色器,因为我们需要在片元阶段计算相机视线方向:
#version 150 in vec4 position; in vec3 normal; out vec3 vNormal; out vec3 vWorldPos; uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; uniform mat4 modelMatrix; void main(){ // 计算世界空间下的顶点位置 vWorldPos = (modelMatrix * position).xyz; // 计算世界空间下的法线(用逆矩阵转置避免缩放对法线的影响) vNormal = normalize(mat3(transpose(inverse(modelMatrix))) * normal); gl_Position = projectionMatrix * modelViewMatrix * position; }
3. 片元着色器(fragShader.glsl)
这是实现颜色变化的核心部分,我们在这里计算视线与法线的夹角,并混合颜色:
#version 150 in vec3 vNormal; in vec3 vWorldPos; uniform vec3 cameraPos; out vec4 fragColor; void main(){ // 计算相机到当前片元的视线方向(归一化确保长度为1) vec3 viewDir = normalize(cameraPos - vWorldPos); // 计算法线和视线方向的点积,结果范围[-1,1],取正值只考虑正面角度 float dotProduct = max(dot(vNormal, viewDir), 0.0); // 用点积值混合蓝色和红色:dotProduct=1时全红,dotProduct=0时全蓝 vec3 color = mix(vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), dotProduct); fragColor = vec4(color, 1.0); }
关键部分解释
- 视线方向计算:
cameraPos - vWorldPos得到从片元指向相机的向量,归一化后确保点积结果准确反映夹角的余弦值。 - 点积的作用:
dot(vNormal, viewDir)的结果等于cos(theta),theta是法线和视线的夹角。当theta=0°(正对)结果为1,theta=90°(侧面)结果为0。 - 颜色混合:用
mix函数实现线性插值,根据夹角平滑过渡两种颜色,自然适配矩形、球体等任意带法线的几何体。
扩展玩法
- 如果想让背面也有同样效果,可以把
max(dotProduct, 0.0)改成abs(dotProduct),这样背面的角度也会参与颜色计算。 - 调整过渡曲线:比如用
pow(dotProduct, 2.0)让红色区域更集中,或sqrt(dotProduct)让过渡更平缓。 - 替换颜色组合:比如
mix(vec3(0.2, 0.8, 0.2), vec3(0.8, 0.2, 0.2), dotProduct)实现绿到红的变化。
内容的提问来源于stack exchange,提问作者TheInnerParty
相关产品推荐
相关产品推荐

