[Solved] How to make an interpolated Triangle w/ vertex & pixel shaders?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
AllanAxolotls
Prole
Posts: 7
Joined: Sun Jan 22, 2023 3:42 pm

Re: [Solved] How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by AllanAxolotls »

Exactly when you replied I also realized that I just had to change that value. Sorry but thanks for the solution!
RNavega
Party member
Posts: 355
Joined: Sun Aug 16, 2020 1:28 pm

Re: How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by RNavega »

Looking at that Shader Variables page you linked to, it mentions a "VaryingColor" variable predefined by the Löve base shader code. It's used in these lines:
- https://github.com/love2d/love/blob/02d ... r.lua#L227
- https://github.com/love2d/love/blob/02d ... r.lua#L233

Looking at how it's used in there, I think we can spare that vColor variable and use VaryingColor directly, something like this:
(Thanks for the template code by the way)

Code: Select all

local lg = love.graphics

-- Create attributes of the triangle

local attributes = {
    {"VertexPosition", "float", 2},
    {"VertexColor", "float", 4},
}

-- Create the triangle vertices.
-- The attributes per-vertex are X, Y, R, G, B, A.

local vertices = {
    {-0.5, -0.5, 1.0, 0.0, 0.0, 1.0},
    {0.5, -0.5, 0.0, 1.0, 0.0, 1.0},
    {0.0, 0.5, 0.0, 0.0, 1.0, 1.0}
}

-- Create a new mesh to hold the triangle

local triangle = lg.newMesh(attributes, vertices, "triangles", "static")

-- Set up the color interpolation pixel shader.
local shader = lg.newShader([[
    vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
        return VaryingColor;
    }
]])

function love.draw()

    lg.setShader(shader)
    lg.draw(triangle,lg.getWidth()/2,lg.getHeight()/2,0,200,200)
    lg.setShader()

end
User avatar
Bigfoot71
Party member
Posts: 287
Joined: Fri Mar 11, 2022 11:07 am

Re: [Solved] How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by Bigfoot71 »

Yes I specified it in my previous post, it was to illustrate how to pass a variable from vertex shader to the fragment shader.
My avatar code for the curious :D V1, V2, V3.
RNavega
Party member
Posts: 355
Joined: Sun Aug 16, 2020 1:28 pm

Re: [Solved] How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by RNavega »

Ah, I missed that! Thanks.
AllanAxolotls
Prole
Posts: 7
Joined: Sun Jan 22, 2023 3:42 pm

Re: How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by AllanAxolotls »

Bigfoot71 wrote: Mon Jan 23, 2023 12:14 pm Note that I added the vertex shader as an example to show how to pass a variable from the vertex to the fragment, but the variable `VertexColor` to a correspondence directly offered by Löve accessible in the fragment shader which is `VaryingColor` so the last shader that I proposed can be simplified like this:

Code: Select all

local shader = lg.newShader([[
    vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
        return VaryingColor;
    }
]])
Hence the fact that I say that doing it directly in OpenGL allows you to learn more because Löve does a lot of work for us at this level. After once this kind of basic acquired Löve still allows you to progress faster on learning because this kind of tests are faster to perform in Lua but you must already have these basics. Hence once again the fact that I wonder about the usefulness of following an OpenGL tutorial with Löve2d, after again it's only my opinion, I may be wrong.

Edit: I'm thinking about it but if you really want to do all that in Löve2d I can only recommend this blog post on shaders in Löve2d which is very useful! ^^ https://blogs.love2d.org/content/beginn ... de-shaders
After reading https://www.love2d.org/wiki/Shader_Variables I couldn't find "VaryingNormal" anywhere, and I need that for the lighting to be smooth. Is there any way to interpolate the normal when it gets passed from the VertexShader to the FragmentShader / PixelShader? I tried using "varying vec3 Normal;" and then in the position() function "Normal = VertexNormal;". But I don't know exactly why, but I had to include "attribute vec3 VertexNormal;" in order for it not to scream: "undeclared identifier", even though I included it in the vertex attributes of the mesh and also added the normal values at the mesh construction. Then inside the FragmentShader: "varying vec3 Normal;" and inside effect() I just use the value and do my light equations, but it doesn't seem to be interpolated which is a problem. Basically, I want the normal to interpolate in a way that its origin comes from the current pixel in the FragmentShader. Then I later do a dot product to see if the light is pointing directly at it or not. How would I interpolate a normal if I don't have a "VaryingNormal" variable? Or did I do something wrong with the normal?

Code: Select all

local MeshAttributes = {
    {"VertexPosition", "float", 3},
    {"VertexTexCoord", "float", 2},
    {"VertexNormal", "float", 3},
    {"VertexColor", "byte", 4},
}

local shader = LG.newShader([[

// -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
// Vertex Shader 
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- //

extern mat4 World;
varying vec3 Normal;
attribute vec3 VertexNormal;
varying vec3 LocalPos;

vec4 position(mat4 transform_projection, vec4 Position) {
    Normal = VertexNormal;
    vec4 Transformed = World * Position;
    vec4 ScreenSpace = transform_projection * Transformed;
    ScreenSpace.z = -ScreenSpace.z;
    LocalPos = vec3(Position);
    return ScreenSpace;
}

]], [[


// -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
// Fragment Shader 
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- //

struct BaseLight {
    vec3 Color;
    float AmbientIntensity;
};

struct DirLight {
    vec3 Color;
    vec3 Direction;
    float AmbientIntensity;
    float DiffuseIntensity;
};

// Light Arrays
extern BaseLight BaseLights[1];
extern DirLight DirLights[1];

// Material
extern vec3 MaterialAmbientColor;
extern vec3 MaterialDiffuseColor;
extern vec3 MaterialSpecularColor;

extern vec3 CameraLocalPos;

varying vec3 Normal;
varying vec3 LocalPos;

vec4 effect(vec4 Color, Image image, vec2 uvs, vec2 screen_coords){
    // Interpolation: return VaryingColor;

    BaseLight gLight = BaseLights[0];
    DirLight gLight2 = DirLights[0];

    //vec4 LightColor = gLight.Color * gLight.AmbientIntensity;
    vec3 LocalAmbientColor = gLight2.Color * gLight2.AmbientIntensity * MaterialAmbientColor;

    float DiffuseFactor = dot(normalize(Normal), -gLight2.Direction);
    vec3 LocalDiffuseColor = vec3(0, 0, 0);
    vec3 LocalSpecularColor = vec3(0, 0, 0);

    if (DiffuseFactor > 0){
        LocalDiffuseColor = gLight2.Color * gLight2.DiffuseIntensity * MaterialDiffuseColor * DiffuseFactor;

        vec3 PixelToCamera = normalize(CameraLocalPos - LocalPos);
        vec3 LightReflect = normalize(reflect(gLight2.Direction, Normal));
        float SpecularFactor = dot(PixelToCamera, LightReflect);

        if (SpecularFactor > 0) {
            // texture is grayscale
            //float SpecularExponent = Texel(SpecularTexture, uvs).r * 255.0;
            SpecularFactor = pow(SpecularFactor, 3.0);
            LocalSpecularColor = gLight2.Color * MaterialSpecularColor * SpecularFactor;
        }
    }

    return Texel(image, uvs) * clamp(vec4(LocalAmbientColor + LocalDiffuseColor + LocalSpecularColor, 1.0f), 0, 1);
}

]])
User avatar
Bigfoot71
Party member
Posts: 287
Joined: Fri Mar 11, 2022 11:07 am

Re: How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by Bigfoot71 »

Not having all your code to try from what I understand you are unable to pass the `VertexNormal` attribute to the fragment shader, to do this you just need to do something like this:

Code: Select all

attribute vec3 VertexNormal;                                            // Mesh attribute
varying vec3 VaryingNormal;                                             // "Varying value" that will be used to pass VertexNormal to the fragment shader

vec4 position( mat4 transform_projection, vec4 vertex_position ) {
    VaryingNormal = VertexNormal;                                       // Set "varying value" with attribute
    return transform_projection * vertex_position;
}

Code: Select all

varying vec3 VaryingNormal;                                             // Redecalate the "varying value" here to be able to use it

vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
    // Make your calculations
}
Is that right?

Edit: By reviewing your code I think in fact that you have understood this, but not being able to test it I am not sure to tell you where the problem comes from.

If you don't want/can't share it in full you can always go see the library I quoted (g3d) to see how it handles all this, do your tests with then once you have understood start again from scratch with your knowledge. Here the link to are vertex shader.

If not, share your code with us so we can better understand the problem ^^
My avatar code for the curious :D V1, V2, V3.
AllanAxolotls
Prole
Posts: 7
Joined: Sun Jan 22, 2023 3:42 pm

Re: How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by AllanAxolotls »

Bigfoot71 wrote: Thu Jan 26, 2023 12:00 pm Not having all your code to try from what I understand you are unable to pass the `VertexNormal` attribute to the fragment shader, to do this you just need to do something like this:

Code: Select all

attribute vec3 VertexNormal;                                            // Mesh attribute
varying vec3 VaryingNormal;                                             // "Varying value" that will be used to pass VertexNormal to the fragment shader

vec4 position( mat4 transform_projection, vec4 vertex_position ) {
    VaryingNormal = VertexNormal;                                       // Set "varying value" with attribute
    return transform_projection * vertex_position;
}

Code: Select all

varying vec3 VaryingNormal;                                             // Redecalate the "varying value" here to be able to use it

vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
    // Make your calculations
}
Is that right?

Edit: By reviewing your code I think in fact that you have understood this, but not being able to test it I am not sure to tell you where the problem comes from.

If you don't want/can't share it in full you can always go see the library I quoted (g3d) to see how it handles all this, do your tests with then once you have understood start again from scratch with your knowledge. Here the link to are vertex shader.

If not, share your code with us so we can better understand the problem ^^
Here's the "Render" script, btw I call all these functions from the main.lua file.

Code: Select all

 local Render = {}

--// Requires
local mlib = require("mlib")

--// Read-Only Settings
local MeshAttributes = {
    {"VertexPosition", "float", 3},
    {"VertexTexCoord", "float", 2},
    {"VertexNormal", "float", 3},
    {"VertexColor", "byte", 4},
}

--// Get Shader Files
local VertexShader = love.filesystem.read("VertexShader.txt")
local FragmentShader = love.filesystem.read("FragmentShader.txt")

--// Löve
local LG = love.graphics
local LI = love.image
local LK = love.keyboard
local LW = love.window

--// Other optimisations
local sin = math.sin
local cos = math.cos
local tan = math.tan
local rad = math.rad
local abs = math.abs
local min = math.min
local max = math.max

local pi = math.pi
local sqrt = math.sqrt
local floor = math.floor
local ceil = math.ceil
local huge = math.huge
local random = math.random
local num = tonumber

--// Shaders
local MainShader = LG.newShader(VertexShader, FragmentShader)

--// Globals
local CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
local NUMBERS = "1234567890"
local SYMBOLS = "!@#$%^&*()'=-+_/.,<>"
local KEYS = CHARACTERS .. NUMBERS .. SYMBOLS

--// Variables
local Canvas = nil
local DepthCanvas = nil

local LightWorkspace = {}

--// Engine Variables
Render.Workspace = {}
Render.MainCamera = nil
Render.ObjectMaterials = {}
Render.ProjectionMatrix = nil

--// Config Settings
Render.ScreenWidth = 1920
Render.ScreenHeight = 1080
Render.FullScreen = true
Render.Resizable = false
Render.DepthType = "depth32f"

Render.zNear = 1
Render.zFar = 1000
Render.FOV = 30

Render.Spawn = mlib.Vector3(0, 0, 0)

Render.EscapeKey = "escape"
Render.RestartKey = "r"

Render.ObjectPath = "Objects"
Render.TexturePath = "Textures"

Render.AlwaysPanning = false
Render.MousePanButton = 2
Render.MouseSensivity = 0.7
Render.CameraPitch = 0
Render.CameraYaw = 0
Render.CameraRoll = 0
Render.TurnSpeed = -0.5

--// Utility
function Render.SplitString(x, Seperator)
	--if Seperator == nil then Seperator = "%s" end
	local t = {}
	local i = 1
	for str in string.gmatch(x, "([^".. Seperator .."]+)") do t[i] = str; i = i + 1 end
	return t
end; local SplitString = Render.SplitString

function Render.ArrayToString(Array)
    local Result = ""

    local i = 0
    for k, v in pairs(Array) do
        i = i + 1
        if type(v) == "table" then v = Render.ArrayToString(v) end
        if type(v) ~= "userdata" then Result = Result .. "\n[" .. k .. "]: " .. v end
        if i ~= #Array then Result = Result .. ", " end
    end

    if Result == "" and i == 0 then Result = "nil" end
    return Result
end; local ArrayToString = Render.ArrayToString

function Render.RandomColor()
    local R = random(0, 100) / 100
    local G = random(0, 100) / 100
    local B = random(0, 100) / 100
    return R, G, B, 1
end; local RandomColor = Render.RandomColor

function Render.Lock(x, MinValue, MaxValue)
    return max(min(x, MaxValue), MinValue)
end; local lock = Render.Lock

--// Engine Classes
local BaseLight = {Type = "BaseLight"}
BaseLight.__index = BaseLight

function Render.BaseLight()
    local self = setmetatable({
        Color = {1, 1, 1};
        AmbientIntensity = 1;
    }, BaseLight)
    return self
end

local DirectionalLight = setmetatable({Type = "DirectionalLight"}, BaseLight)
DirectionalLight.__index = DirectionalLight

function Render.DirectionalLight()
    local self = setmetatable({
        Color = {1, 1, 1};
        AmbientIntensity = 1;
        DiffuseIntensity = 1;
        WorldDirection = mlib.Vector3(0, 0, 0);
        LocalDirection = mlib.Vector3(0, 0, 0);

    }, DirectionalLight)
    LightWorkspace[#LightWorkspace+1] = self
    return self
end

function DirectionalLight:Calc(WM)
     --// Get rid of translation in matrix, because not required
     local World3 = mlib.Convert(WM, "Matrix3")

     --[[
     local World3 = {
        WM[1], WM[5], WM[9],
        WM[2], WM[6], WM[10],
        WM[3], WM[7], WM[11]
    }
    ]]

    self.LocalDirection = (World3 * self.WorldDirection):Normalise()
end

local Mesh = {}
Mesh.__index = Mesh

local function GetObjectByID(ID)
    for Alias, Object in pairs(Render.Workspace) do if Object.ID == ID then return Object end end
end

function Mesh:SetAlias(Alias)
    if Render.Workspace[Alias] ~= nil then
        local NewID = Alias
        local Found = false

        while true do
            for i = 1, #KEYS, 1 do --// Go through every character and check if it works
                local Attempt = NewID .. KEYS[i]

                if not GetObjectByID(Attempt) then
                    NewID = Attempt
                    Found = true
                    break
                end
            end

            if Found == true then break end
            NewID = NewID .. "_" --// We can add any symbol technically
        end

        self.ID = NewID
    else
        self.ID = Alias
    end
end

function Mesh:InsertWorkspace(Alias)
    self:SetAlias(Alias)
    Render.Workspace[self.ID] = self
end

function Mesh:SetPosition(X, Y, Z) self.Position[1] = X; self.Position[2] = Y; self.Position[3] = Z end
function Mesh:SetRotation(X, Y, Z) self.Rotation[1] = X; self.Rotation[2] = Y; self.Rotation[3] = Z end
function Mesh:SetSize(X, Y, Z) self.Size[1] = X; self.Size[2] = Y; self.Size[3] = Z end

function Mesh:Rotate(AngleX, AngleY, AngleZ)
    self.Rotation[1] = self.Rotation[1] + (AngleX or 0)
    self.Rotation[2] = self.Rotation[2] + (AngleY or 0)
    self.Rotation[3] = self.Rotation[3] + (AngleZ or 0)
end

local Material = {}
Material.__index = Material

function Render.Material()
    local self = setmetatable({
        AmbientColor = {0, 0, 0};
        DiffuseColor = {0, 0, 0};
        SpecularColor = {0, 0, 0};
    }, Material)
    return self
end

function Render.ReadMaterial(FilePath)
    --// This function will go through all the data in the .mtl file
    --// it will create new materials and insert all of them inside an ObjectMaterials array
    --// from there the MeshLoadObjectFile can use these materials with "usemtl"
    local FileContent = nil
    local Success, ErrorMessage = pcall(function() FileContent = love.filesystem.read(FilePath) end)
    if not Success then return 0, "File not found or cant't read file!" end

    local NewMaterial = nil
    local MaterialName = ""

    local Lines = SplitString(FileContent, "\n")
    for _, Line in ipairs(Lines) do
        local Tokens = SplitString(Line, " ")
        local Prefix = Tokens[1]

        if Prefix == "newmtl" then
            --// Put Material inside ObjectMaterials array
            MaterialName = Tokens[2]
            if NewMaterial ~= nil then assert(Render.ObjectMaterials[MaterialName] ~= nil, "Material with name already exists!") end
            NewMaterial = Render.Material()
            Render.ObjectMaterials[MaterialName] = NewMaterial
        elseif Prefix == "Ns" then
        elseif Prefix == "Ka" then --// AmbientColor
            NewMaterial.AmbientColor[1] = Tokens[2]
            NewMaterial.AmbientColor[2] = Tokens[3]
            NewMaterial.AmbientColor[3] = Tokens[4]
        elseif Prefix == "Kd" then --// Diffuse
            NewMaterial.DiffuseColor[1] = Tokens[2]
            NewMaterial.DiffuseColor[2] = Tokens[3]
            NewMaterial.DiffuseColor[3] = Tokens[4]
        elseif Prefix == "Ks" then --// Specular
            NewMaterial.SpecularColor[1] = Tokens[2]
            NewMaterial.SpecularColor[2] = Tokens[3]
            NewMaterial.SpecularColor[3] = Tokens[4]
        elseif Prefix == "Ke" then
        elseif Prefix == "Ni" then
        elseif Prefix == "d" then --// Opaque
        elseif Prefix == "illum" then
        elseif Prefix == "map_Bump" then
        elseif Prefix == "map_Kd" then
        elseif Prefix == "map_Ns" then
        elseif Prefix == "refl" then
        elseif Prefix == "Material" then --// Material Color
        end
    end
end

function Render.FormatImageName(Name, Directory)
    local Result = Directory .. "/" .. Name
    local x = string.gsub(Result, "_diff", "") --// Append the TexturePath
    --\\ Roblox appends _diff to all of their textures when exporting for some reason, so we get rid of it
    --// There is no easy way to get a file without extension in love2d, so defualt is png
    Result = Result .. ".png"
    return Result
end

function Render.LoadObject(ObjectName, FixedTexture)
     --// We get the folder that the .obj is located in, inside the Objects folder
    --// Then we get the .obj file inside of that folder, Quite complicated lol
    local Directory = Render.ObjectPath .. "/" .. ObjectName .. "/"
    local TextureDirectory = Directory .. "Textures/"
    local ObjectFile = Directory .. ObjectName .. ".obj"
    local FileContent = love.filesystem.read(ObjectFile)
    if FileContent == nil then return end
    --assert(FileContent == nil, "File not found or can't read file!")

    --// Find Material File, and use it if it's found
    Render.ReadMaterial(Directory .. ObjectName .. ".mtl")

    --// Create caches
    local Vertices = {}
    local Normals = {}
    local Textures = {}

    local ObjectMesh = Render.Mesh(ObjectName)
    local Meshes = {}

    --// If FixedTexture isn't included, this will result in being nil
    local TextureData = FixedTexture
    local MaterialName = ""

    local Lines = SplitString(FileContent, "\n")
    for _, Line in ipairs(Lines) do
        local NewMesh = nil

        local Tokens = SplitString(Line, " ")
        local Prefix = Tokens[1]

        if Prefix == "v" then --// Create Vertex
            Vertices[#Vertices+1] = mlib.Vector3(num(Tokens[2]), num(Tokens[3]), num(Tokens[4]))
        elseif Prefix == "f" then --// Create face of vertices
            local Segments1 = SplitString(Tokens[2], "/")
            local Segments2 = SplitString(Tokens[3], "/")
            local Segments3 = SplitString(Tokens[4], "/")

            ObjectMesh.Triangles[#ObjectMesh.Triangles+1] = {
                --// Vertex Data
                Vertices[num(Segments1[1])];
                Vertices[num(Segments2[1])];
                Vertices[num(Segments3[1])];
                --// Texture Data
                Textures[num(Segments1[2])];
                Textures[num(Segments2[2])];
                Textures[num(Segments3[2])];
                TextureData;
                --// Normal Data
                Normals[num(Segments1[3])];
                Normals[num(Segments2[3])];
                Normals[num(Segments3[3])];
            }

        elseif Prefix == "vn" then --// Create Normal
            Normals[#Normals+1] = mlib.Vector3(num(Tokens[2]), num(Tokens[3]), num(Tokens[4]))
        elseif Prefix == "vt" then --// Create UV coordinates
            Textures[#Textures+1] = mlib.Vector2(num(Tokens[2]), num(Tokens[3]))
        elseif Prefix == "g" then --// Creates new object group for upcoming faces
            --// If object is empty, don't create a new one just yet
            if #Vertices ~= 0 then NewMesh = Tokens[2] end
            ObjectMesh:SetAlias(Tokens[2]) --// Name the object accordingly
            --\\ We handle objects like groups in this case

            --// Search for texture if there is any
            local Data = nil
            local Success, ErrorMessage = pcall(function()
                Data = LG.newImage(Render.FormatImageName(Tokens[2], TextureDirectory))
            end)

            --// If texture is found, set it
            if Success and not FixedTexture then TextureData = Data end

        elseif Prefix == "usemtl" or Prefix == "mtllib" then --// Set Material of upcoming triangles
           --// Set the material of the object
           --MaterialName = Tokens[2]
           MaterialName = Tokens[2]
           ObjectMesh.ObjectMaterial = Render.ObjectMaterials[MaterialName]
        end

        if NewMesh ~= nil then
            --// If NewMesh isn't nil then create a new mesh
            --// Note: Caches don't have to be reset, index just goes up even though new object is formed
            Meshes[#Meshes+1] = ObjectMesh
            ObjectMesh = Render.Mesh(NewMesh)
            ObjectMesh.ObjectMaterial = ObjectMaterials[MaterialName] --// !: may break
            Triangles = ObjectMesh.Triangles
        end
    end

    --// Add Final Mesh
    Meshes[#Meshes+1] = ObjectMesh

    return Meshes
end

function Render.Mesh(Alias, PX, PY, PZ, RX, RY, RZ, SX, SY, SZ)
    local self = setmetatable({
        ID = 0; --// Reference of object in Workspace
        Triangles = {};

        Position = mlib.Vector4(PX or 0, PY or 0, PZ or 0);
        Rotation = mlib.Vector4(RX or 0, RY or 0, RZ or 0);
        Size     = mlib.Vector4(SX or 1, SY or 1, SZ or 1);

        MeshFormats = {};
        ObjectMaterial = nil;
    }, Mesh)
    self:InsertWorkspace(Alias)
    return self
end

--// Camera
local Camera = {}
Camera.__index = Camera

function Render.Camera(X, Y, Z)
    local self = setmetatable({
        pos = mlib.Vector3(X, Y, Z),
        target = mlib.Vector3(0, 0, 1),
        up = mlib.Vector3(0, 1, 0),
        speed = 5
    }, Camera)
    return self
end

function Camera:SetPosition(X, Y, Z)
    self.pos.x = X
    self.pos.y = Y
    self.pos.z = Z
    return self
end

function Camera:RotationTransform()
    local Pos, Target, Up = self.pos, self.target, self.up
    local CameraTranslation = mlib.TranslationMatrix4(-Pos.x, -Pos.y, -Pos.z)
    local CameraRotateTrans = mlib.CameraTransform(Target, Up)
    return CameraRotateTrans * CameraTranslation
end

--// More Advanced Functions

--// Setters and Init
function Render.SetProjectionMatrix()
    Render.ProjectionMatrix = mlib.ProjectionMatrix(Render.zNear, Render.zFar, Render.FOV)
end

function Render.Init(ScreenWidth, ScreenHeight)
    Render.ScreenWidth = ScreenWidth or 1920
    Render.ScreenHeight = ScreenHeight or 1080

    LW.setMode(ScreenWidth, ScreenHeight, {fullscreen=Render.FullScreen,resizable=Render.FullScreen})
    Canvas = LG.newCanvas()
    DepthCanvas = LG.newCanvas(ScreenWidth, ScreenHeight, {type="2d",format="depth32f",readable=true})
    LG.setMeshCullMode("back")

    Render.MainCamera = Render.Camera(Render.Spawn.x, Render.Spawn.y, Render.Spawn.z)

    --// Create Sun and Skybox
end

function Render.SetResolution(ScreenWidth, ScreenHeight)
    Render.ScreenWidth = ScreenWidth or 1920
    Render.ScreenHeight = ScreenHeight or 1080
end

--// Controls
local Panning = false

function Render.MousePressed(X, Y, Button) --// If mouse is down (not released yet)
    if Button ~= Render.MousePanButton then return end
    Panning = true
    MouseX, MouseY = X, Y
    StartPanX, StartPanY = X, Y
    love.mouse.setGrabbed(true)
end

function Render.MouseReleased(X, Y, Button)
    if Button ~= Render.MousePanButton then return end
    Panning = false
    love.mouse.setGrabbed(false)
end

function Render.UpdateMousePan(dt)
    if Render.AlwaysPanning or Panning then
        --love.mouse.setVisible(false)
        NewMouseX, NewMouseY = love.mouse.getX(), love.mouse.getY()
        local DifferenceX, DifferenceY
        DifferenceX = NewMouseX - MouseX
        DifferenceY = NewMouseY - MouseY

        Render.CameraYaw = Render.CameraYaw - DifferenceX * dt * Render.MouseSensivity
        Render.CameraPitch = lock(Render.CameraPitch - DifferenceY * dt * Render.MouseSensivity, -1.5, 1.5)

        --// Reset Mouse Position
        --local OriginX, OriginY = Allos.VisualWidth / 2, Allos.VisualHeight / 2
        love.mouse.setPosition(Render.ScreenWidth / 2, Render.ScreenHeight / 2)
    end
    MouseX, MouseY = Render.ScreenWidth / 2, Render.ScreenHeight / 2
end

function Render.UpdateControls(dt)
    local PlayerCamera = Render.MainCamera
    local Target, Up, Speed = PlayerCamera.target, PlayerCamera.up, PlayerCamera.speed * dt
    if LK.isDown('w') then PlayerCamera.pos = PlayerCamera.pos + (Target * Speed) end
    if LK.isDown('s') then PlayerCamera.pos = PlayerCamera.pos - (Target * Speed) end
    if LK.isDown("space") then PlayerCamera.pos = PlayerCamera.pos + Up * Speed end
    if LK.isDown("lshift") or LK.isDown("rshift") then PlayerCamera.pos = PlayerCamera.pos - Up * Speed end
    if LK.isDown('a') then
        local Left = Up:Cross(Target) --// Flipped (Target, Up)
        Left:Normalise()
        PlayerCamera.pos = PlayerCamera.pos + Left * Speed
    end
    if LK.isDown('d') then
        local Right = Target:Cross(Up) --// Flipped (Up, Target)
        Right:Normalise()
        PlayerCamera.pos = PlayerCamera.pos + Right * Speed
    end

    if LK.isDown("up") then Render.CameraPitch = lock(Render.CameraPitch - Render.TurnSpeed * dt, -1.5, 1.5) end
    if LK.isDown("down") then Render.CameraPitch = lock(Render.CameraPitch + Render.TurnSpeed * dt, -1.5, 1.5) end
    if LK.isDown("right") then Render.CameraYaw = Render.CameraYaw + Render.TurnSpeed * dt end
    if LK.isDown("left") then Render.CameraYaw = Render.CameraYaw - Render.TurnSpeed * dt end

    if LK.isDown(Render.EscapeKey) then love.event.quit(1) end
    if LK.isDown(Render.RestartKey) then love.event.quit('restart') end

    local CameraRotationX = mlib.XRotationMatrix4(Render.CameraPitch)
    local CameraRotationY = mlib.YRotationMatrix4(Render.CameraYaw)
    local CameraRotationZ = mlib.ZRotationMatrix4(Render.CameraRoll)

    --// We transform our target direction normal by a rotation matrix so it's pointing in the correct direction
    --// We can later add on to this with another rotation matrix to rotate up and down
    local LookDirection = CameraRotationZ * (CameraRotationY * (CameraRotationX * mlib.Vector4(0, 0, 1, 1)))
    --\\ We want yaw to go after pitch

    PlayerCamera.target.x = LookDirection.x
    PlayerCamera.target.y = LookDirection.y
    PlayerCamera.target.z = LookDirection.z
end

--// Draw
function Render.RenderScene(IteratorList)
    IteratorList = IteratorList or Render.Workspace
    LG.setCanvas({Canvas, depthstencil=DepthCanvas, depth=true})
    LG.clear()
    LG.setDepthMode("lequal", true)
    LG.setShader(MainShader)

    local MainCamera = Render.MainCamera

    for ID, Object in pairs(IteratorList) do
        local WM = mlib.WorldMatrix(Object)
        local WVP = Render.ProjectionMatrix * (MainCamera:RotationTransform() * WM)

        MainShader:send("World", WVP)

        --// Go through all lights and send data
        local dir = 0
        for _, Light in ipairs(LightWorkspace) do
            if Light.Type == "DirectionalLight" then
                Light:Calc(WM)
                local T = "DirLights[" .. dir .. "]."
                MainShader:send(T .. "AmbientIntensity", Light.AmbientIntensity)
                MainShader:send(T .. "DiffuseIntensity", Light.DiffuseIntensity)
                MainShader:send(T .. "Color", Light.Color)
                MainShader:send(T .. "Direction", Light.LocalDirection:ToArray())
                dir = dir + 1
            end
        end

        local ObjectMaterial = Object.ObjectMaterial
        MainShader:send("MaterialDiffuseColor",  mlib.Color3ToArray4(ObjectMaterial.DiffuseColor))
        MainShader:send("MaterialAmbientColor", mlib.Color3ToArray4(ObjectMaterial.AmbientColor))
        MainShader:send("MaterialSpecularColor", mlib.Color3ToArray4(ObjectMaterial.SpecularColor))
        MainShader:send("CameraPos", Render.MainCamera.pos:ToArray(1))

        if #Object.MeshFormats == 0 and #Object.Triangles > 0 then
            for i, Triangle in ipairs(Object.Triangles) do
                local Vertices = {
                    {
                        Triangle[1].x, Triangle[1].y, Triangle[1].z,
                        Triangle[4].x, 1-Triangle[4].y,
                        Triangle[8].x, Triangle[8].y, Triangle[8].z,
                        1, 1, 1, 1
                    },
                    {
                        Triangle[2].x, Triangle[2].y, Triangle[2].z,
                        Triangle[5].x, 1-Triangle[5].y,
                        Triangle[9].x, Triangle[9].y, Triangle[9].z,
                        1, 1, 1, 1
                    },
                    {
                        Triangle[3].x, Triangle[3].y, Triangle[3].z,
                        Triangle[6].x, 1-Triangle[6].y,
                        Triangle[10].x, Triangle[10].y, Triangle[10].z,
                        1, 1, 1, 1
                    },
                }

                local MeshTriangle = LG.newMesh(MeshAttributes, Vertices, "triangles", "static")
                MeshTriangle:setTexture(Triangle[7])
                Object.MeshFormats[i] = MeshTriangle
            end
        end

        for _, Triangle in ipairs(Object.MeshFormats) do
            LG.draw(Triangle, Render.ScreenWidth / 2, Render.ScreenHeight / 2, 0, 100, 100)
        end
    end

    LG.setShader()
    LG.setCanvas()
end

function Render.OutputRender()
    LG.setColor(1,1,1)
    LG.setDepthMode()
    LG.draw(Canvas, 0,0,0, 1,1)
    LG.print(ArrayToString(Render.MainCamera.pos))
end

return Render 
The fragmentcode can be seen in the reply before this one, I didn't change it, except for changing the name of the vertexNormal varying variable to VaryingNormal. The normal is not getting interpolated per pixel. I'm not 100% sure if it's the lighting code or not. Btw, the shadercode is receiving the light information correctly. I've looked through the g3d code before, but I couldn't really find any fragmentshader code, only the vertexshader code. g3d does it in roughly the same way so I don't really know what's wrong.
RNavega
Party member
Posts: 355
Joined: Sun Aug 16, 2020 1:28 pm

Re: How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by RNavega »

VertexColor is defined in the Löve base shader as a vec4. That's a 'float' data-type, not byte.

I don't know if that'll fix it, but I'd also try having the shader plug the vertex normals into the varying color, as a visual debug to see if the data is coming in right.
User avatar
Sasha264
Party member
Posts: 131
Joined: Mon Sep 08, 2014 7:57 am

Re: How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by Sasha264 »

Good day! :3

Well... I took your shaders + your mesh format exactly how they were, symbol by symbol, plus made a simple example which draw only 2 triangles with them.
And it works! (aside from VertexColor, but looks like it is just not used in your shaders)

I animated light position, so it is moving circles.
And there is clearly a difference between these 2 triangles around top corner, and only around top corner, exactly where I made a difference between normals, so they do interpolate.
result.png
result.png (86.39 KiB) Viewed 2545 times
Full code:
LoveNormals.love
(2.01 KiB) Downloaded 90 times
Based on that looks like the problem is not in your shaders or mesh format.
Is it all correct with model files reading / normals calculation?:
  • Maybe your normals look inside of the object so you have flat dark ambient color.
  • Maybe your normals are not nearly perpendicular to the surface, as often assumed to be, but closer to parallel to it.
AllanAxolotls
Prole
Posts: 7
Joined: Sun Jan 22, 2023 3:42 pm

Re: How to make an interpolated Triangle w/ vertex & pixel shaders?

Post by AllanAxolotls »

Sasha264 wrote: Sat Jan 28, 2023 6:05 am Good day! :3

Well... I took your shaders + your mesh format exactly how they were, symbol by symbol, plus made a simple example which draw only 2 triangles with them.
And it works! (aside from VertexColor, but looks like it is just not used in your shaders)

I animated light position, so it is moving circles.
And there is clearly a difference between these 2 triangles around top corner, and only around top corner, exactly where I made a difference between normals, so they do interpolate.
result.png

Full code:
LoveNormals.love

Based on that looks like the problem is not in your shaders or mesh format.
Is it all correct with model files reading / normals calculation?:
  • Maybe your normals look inside of the object so you have flat dark ambient color.
  • Maybe your normals are not nearly perpendicular to the surface, as often assumed to be, but closer to parallel to it.
You're right! When I used a different .obj file, and added the movement of the light, it was getting interpolated correctly. So it's very likely that the vertex normal data isn't correct inside my other obj file. Thank you so much for the help! I will have to try to fix the obj file I guess. :rofl:
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 3 guests